diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index f73bfa7927..c3ef9fa088 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -111,6 +111,7 @@ jobs:
arch: x86_64
runner: "ubuntu-22.04"
ghc: "8.10.7"
+ hash: 'sha256:5c8b2c0a6c745bc177669abfaa716b4bc57d58e2ea3882fb5da67f4d59e3dda5'
should_run: ${{ !(github.ref == 'refs/heads/stable' || startsWith(github.ref, 'refs/tags/v')) }}
- os: 22.04
os_underscore: 22_04
@@ -118,24 +119,28 @@ jobs:
runner: "ubuntu-22.04"
should_run: true
ghc: ${{ needs.variables.outputs.GHC_VER }}
+ hash: 'sha256:5c8b2c0a6c745bc177669abfaa716b4bc57d58e2ea3882fb5da67f4d59e3dda5'
- os: 24.04
os_underscore: 24_04
arch: x86_64
runner: "ubuntu-24.04"
should_run: true
ghc: ${{ needs.variables.outputs.GHC_VER }}
+ hash: 'sha256:98ff7968124952e719a8a69bb3cccdd217f5fe758108ac4f21ad22e1df44d237'
- os: 22.04
os_underscore: 22_04
arch: aarch64
runner: "ubuntu-22.04-arm"
should_run: true
ghc: ${{ needs.variables.outputs.GHC_VER }}
+ hash: 'sha256:6a62a4157b8775eaf4959cb629e757d32d39d1f4c8ac1b0ddc2510b555cf72f3'
- os: 24.04
os_underscore: 24_04
arch: aarch64
runner: "ubuntu-24.04-arm"
should_run: true
ghc: ${{ needs.variables.outputs.GHC_VER }}
+ hash: 'sha256:68434214381cb38287104e629fe8ee720167dd98cbb36ab1cbbab342515fa6ab'
steps:
- name: Checkout Code
if: matrix.should_run == true
@@ -182,6 +187,7 @@ jobs:
tags: build/${{ matrix.os }}:latest
build-args: |
TAG=${{ matrix.os }}
+ HASH=${{ matrix.hash }}
GHC=${{ matrix.ghc }}
USER_UID=${{ steps.ids.outputs.uid }}
USER_GID=${{ steps.ids.outputs.gid }}
@@ -582,3 +588,92 @@ jobs:
bin_hash: ${{ steps.windows_desktop_build.outputs.package_hash }}
github_ref: ${{ github.ref }}
github_token: ${{ secrets.GITHUB_TOKEN }}
+
+# =========================
+# NodeJS libs release
+# =========================
+
+# Downloads Desktop builds, extracts and archives libraries for NodeJS addon.
+# Depends on Linux/MacOS, executes only on release.
+
+# Secrets:
+# -------
+# NODEJS_REPO_TOKEN
+# Only select repositories: simplex-chat-libs
+# Permissions:
+# * Contents (Read and Write)
+
+ release-nodejs-libs:
+ runs-on: ubuntu-latest
+ needs: [build-linux, build-macos]
+ if: startsWith(github.ref, 'refs/tags/v') && (!cancelled())
+ steps:
+ - name: Checkout current repository
+ uses: actions/checkout@v6
+
+ - name: Install packages for archiving
+ run: sudo apt install -y msitools gcc-mingw-w64
+
+ - name: Build archives
+ run: |
+ INIT_DIR='${{ runner.temp }}/artifacts'
+ RELEASE_DIR='${{ runner.temp }}/release-assets'
+ TAG='${{ github.ref_name }}'
+ URL='https://github.com/${{ github.repository }}/releases/download'
+ PREFIX='${{ github.event.repository.name }}-libs'
+ # Windows-specific
+ FILE_URL='https://raw.githubusercontent.com/${{ github.repository }}/refs/tags/${{ github.ref_name }}'
+
+ # Setup directories
+ mkdir "$INIT_DIR" "$RELEASE_DIR" && cd "$INIT_DIR"
+
+ # Downlaod desktop release
+ curl --proto '=https' --tlsv1.2 -sSf -L "${URL}/${TAG}/simplex-desktop-ubuntu-22_04-x86_64.deb" -o linux.deb
+ curl --proto '=https' --tlsv1.2 -sSf -L "${URL}/${TAG}/simplex-desktop-macos-aarch64.dmg" -o macos-aarch64.dmg
+ curl --proto '=https' --tlsv1.2 -sSf -L "${URL}/${TAG}/simplex-desktop-macos-x86_64.dmg" -o macos-x86_64.dmg
+ curl --proto '=https' --tlsv1.2 -sSf -L "${URL}/${TAG}/simplex-desktop-windows-x86_64.msi" -o windows-x86_64.msi
+
+ # Linux
+ # -----
+ # Extract libraries
+ dpkg-deb -R linux.deb linux-out/ && cd linux-out/opt/simplex/lib/app/resources
+ # Preprare directory
+ mkdir libs && cp *.so libs/
+ # Archive
+ zip -r "${PREFIX}-linux-x86_64.zip" libs
+ # Back to original dir
+ mv "${PREFIX}-linux-x86_64.zip" "$RELEASE_DIR" && cd "$INIT_DIR"
+
+ # MacOS: aarch64
+ # --------------
+ 7z x macos-aarch64.dmg -omacos1-out/ && cd macos1-out/SimpleX/SimpleX.app/Contents/app/resources/
+ mkdir libs && cp *.dylib libs/
+ zip -r "${PREFIX}-macos-aarch64.zip" libs
+ mv "${PREFIX}-macos-aarch64.zip" "$RELEASE_DIR" && cd "$INIT_DIR"
+
+ # Macos: x86_64
+ # -------------
+ 7z x macos-x86_64.dmg -omacos2-out/ && cd macos2-out/SimpleX/SimpleX.app/Contents/app/resources/
+ mkdir libs && cp *.dylib libs/
+ zip -r "${PREFIX}-macos-x86_64.zip" libs
+ mv "${PREFIX}-macos-x86_64.zip" "$RELEASE_DIR" && cd "$INIT_DIR"
+
+ # Windows: x86_64
+ # ---------------
+ msiextract windows-x86_64.msi -C windows-out && cd windows-out/SimpleX/app/resources
+
+ # We need to generate library that exports symbols from Windows dll
+ curl --proto '=https' --tlsv1.2 -sSf -LO "${FILE_URL}/libsimplex.dll.def"
+ x86_64-w64-mingw32-dlltool -d libsimplex.dll.def -l libsimplex.lib -D libsimplex.dll
+
+ mkdir libs && cp *.dll *.lib libs/
+ zip -r "${PREFIX}-windows-x86_64.zip" libs
+ mv "${PREFIX}-windows-x86_64.zip" "$RELEASE_DIR" && cd "$INIT_DIR"
+
+ - name: Create release in libs repo and upload artifacts
+ uses: softprops/action-gh-release@v2
+ with:
+ repository: ${{ github.repository }}-libs
+ tag_name: ${{ github.ref_name }}
+ files: ${{ runner.temp }}/release-assets/*
+ token: ${{ secrets.NODEJS_REPO_TOKEN }}
diff --git a/.gitignore b/.gitignore
index bf565453a5..2f4af38cca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -55,6 +55,7 @@ website/src/img/images/
website/src/images/
website/src/js/lottie.min.js
website/src/js/ethers*
+website/src/file-assets/
website/src/privacy.md
# Generated files
website/package/generated*
@@ -81,3 +82,4 @@ website/.cache
website/test/stubs-layout-cache/_includes/*.js
apps/android/app/release
apps/multiplatform/.kotlin/sessions
+
diff --git a/Dockerfile.build b/Dockerfile.build
index 3ddff59d12..89f8c25101 100644
--- a/Dockerfile.build
+++ b/Dockerfile.build
@@ -1,6 +1,7 @@
# syntax=docker/dockerfile:1.7.0-labs
ARG TAG=24.04
-FROM ubuntu:${TAG} AS build
+ARG HASH=sha256:98ff7968124952e719a8a69bb3cccdd217f5fe758108ac4f21ad22e1df44d237
+FROM ubuntu:${TAG}@${HASH} AS build
### Build stage
diff --git a/README.md b/README.md
index 3364c28284..818ed7142f 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,8 @@
[
](http://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html) [
](https://www.privacyguides.org/en/real-time-communication/#simplex-chat) [
](https://www.whonix.org/wiki/Chat#Recommendation) [
](https://www.kuketz-blog.de/simplex-eindruecke-vom-messenger-ohne-identifier/)
+**[Why we are building SimpleX Network](./docs/WHY.md)**
+
## Welcome to SimpleX Chat!
1. 📲 [Install the app](#install-the-app).
@@ -42,7 +44,7 @@
## Connect to the team
-You can connect to the team via the app using "chat with the developers button" available when you have no conversations in the profile, "Send questions and ideas" in the app settings or via our [SimpleX address](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion). Please connect to:
+You can connect to the team via the app using "chat with the developers button" available when you have no conversations in the profile, "Send questions and ideas" in the app settings or via our [SimpleX address](https://smp6.simplex.im/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw). Please connect to:
- to ask any questions
- to suggest any improvements
@@ -54,7 +56,7 @@ If you are interested in helping us to integrate open-source language models, an
## Join user groups
-You can find the groups created by users in [SimpleX Directory](https://simplex.chat/directory/). It is also available as [SimpleX bot](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) that allows to add your own groups and communities to the directory. We are not responsible for the content shared in these groups.
+You can find the groups created by users in [SimpleX Directory](https://simplex.chat/directory/). It is also available as [SimpleX bot](https://smp4.simplex.im/a#lXUjJW5vHYQzoLYgmi8GbxkGP41_kjefFvBrdwg-0Ok) that allows to add your own groups and communities to the directory. We are not responsible for the content shared in these groups.
**Please note**: The groups below are created for the users to be able to ask questions, make suggestions and ask questions about SimpleX Chat only.
diff --git a/apps/ios/CODE.md b/apps/ios/CODE.md
new file mode 100644
index 0000000000..adb5ef8c42
--- /dev/null
+++ b/apps/ios/CODE.md
@@ -0,0 +1,219 @@
+# Coding and building
+
+You are an expert developer for SimpleX Chat, a privacy-first decentralized messaging platform. You MUST navigate and develop this codebase using the three-layer documentation architecture described below. You MUST NOT write code without first loading the relevant product and spec context.
+
+## Three-Layer Documentation Architecture
+
+### Why this structure exists
+
+LLMs start each session with no persistent understanding of the codebase. Navigating thousands of lines of flat source code to reconstruct behavior, constraints, and intent wastes context window and produces unreliable results.
+
+The `product/`, `spec/`, and source layers form a persistent, structured representation of the system that survives across sessions. Each layer is connected to the next by bidirectional cross-references. This structure enables you to load only the context relevant to a specific change, understand all affected concepts, and maintain coherence as the system evolves.
+
+### The layers
+
+| Layer | Contains | Question it answers |
+|-------|----------|-------------------|
+| `product/` | Capabilities, user flows, views, business rules, glossary | **What** does the system do and why? |
+| `spec/` | Technical design, API contracts, database schema, service internals | **How** is it organized technically? |
+| `Shared/`, `SimpleXChat/`, `SimpleX NSE/` | Executable Swift code (iOS app) | What does it **execute**? |
+| `../../src/Simplex/Chat/` | Haskell core (chat logic, protocol, database) | What does the **core** execute? |
+
+Each layer links to the next:
+- `product/concepts.md` links every concept to its spec docs, source files, and tests in a single table — this is the primary navigation entry point
+- `product/views/*.md` and `product/flows/*.md` each have a **Related spec:** line linking to their most relevant spec documents
+- `product/glossary.md` uses *See: [spec/...]* references and `product/rules.md` uses **Spec:** [spec/...] references to link individual terms and rules down to spec
+- `spec/` documents contain **Source:** headers and inline function links pointing down to source. Line references MUST be clickable by embedding the `#Lxx-Lyy` fragment in the link URL: [`functionName()`](Shared/Model/SimpleXAPI.swift#Lxx-Lyy). You MUST NOT duplicate line numbers in the display text — the URL fragment is sufficient. Why: redundant line numbers in display text create maintenance burden on every line shift.
+- Reverse direction: the Document Map (end of this file) maps source → spec → product
+
+### Navigation workflow
+
+When the user requests any change, you MUST follow these steps before writing any code:
+
+1. **Identify scope.** You MUST read `product/concepts.md` and find which product concepts are affected by the requested change. Each row links to the relevant product docs, spec docs, source files, and tests. Why: concepts.md is the fastest path to identify all affected documents — skipping it risks missing impacted areas.
+
+2. **Load product context.** You MUST read the relevant `product/views/*.md` or `product/flows/*.md` to understand current user-facing behavior. For business constraints, you MUST read `product/rules.md`. Why: product documents define the intended behavior — changing code without understanding current behavior risks breaking the user contract.
+
+3. **Load spec context.** You MUST follow the product → spec links to read the relevant `spec/*.md` or `spec/services/*.md`. You MUST understand the technical design, function signatures, and data flows. Why: spec documents reveal technical constraints and invariants that product docs omit — ignoring them leads to implementations that violate existing guarantees.
+
+4. **Load source context.** You MUST follow the spec → source links (with line numbers) to read the relevant source files. Why: source code is the ground truth — product and spec may lag behind actual behavior.
+
+5. **Identify full impact.** You MUST read `spec/impact.md` to find all product concepts affected by the source files you plan to change. This determines which documents you MUST update after the code change. Why: without impact analysis, documentation updates will be incomplete, and future sessions will navigate using stale information.
+
+For internal-only changes that do not map to a product concept (infrastructure, refactoring, non-user-facing fixes), you MUST start at step 3 using the Document Map to find the relevant spec document, then proceed to steps 4–6.
+
+6. **Implement.** Make the code change in source, then you MUST update all affected documentation as described in the Change Protocol below.
+
+### Key navigation documents
+
+| Document | Purpose | When to read |
+|----------|---------|-------------|
+| `product/concepts.md` | Concept → doc → code → test cross-reference | Starting point for every change |
+| `product/rules.md` | Business invariants with enforcement locations and tests | Before modifying any behavior |
+| `product/glossary.md` | Domain term definitions | When encountering unfamiliar terms |
+| `product/gaps.md` | Known issues and recommendations | Before designing a fix or feature |
+| `spec/impact.md` | Source file → affected product concepts | After identifying which files to change |
+| Document Map (below) | Source ↔ spec ↔ product mapping | When updating documentation |
+
+---
+
+## Code Security
+
+When designing code and planning implementations, you MUST:
+- Apply adversarial thinking, and consider what may happen if one of the communicating parties is malicious. Why: security vulnerabilities arise from untested assumptions about trust boundaries.
+- Formulate an explicit threat model for each change — who can do which undesirable things and under which circumstances. Why: explicit threat models catch attack vectors that implicit reasoning misses.
+
+---
+
+## Code Style
+
+**Follow existing code patterns — you MUST:**
+- Match the style of surrounding code. Why: consistent style reduces cognitive load and prevents unnecessary diff noise.
+- Use Swift structs for value types, classes for reference types, and enums with associated values for variants. Why: correct type choices leverage the type system for compile-time correctness.
+- Prefer exhaustive switch statements over default cases. Why: default cases bypass compiler checks for new enum cases and hide bugs.
+
+**Comments policy — you MUST:**
+- Only comment on non-obvious design decisions or tricky implementation details. Why: redundant comments create maintenance burden and drift from code.
+- Keep function names and type signatures self-documenting. Why: good names eliminate the need for most comments.
+- Assume a competent Swift reader. Why: over-explaining trivial Swift adds noise without value.
+
+**Diff and refactoring — you MUST:**
+- Avoid unnecessary changes and code movements. Why: unnecessary changes increase review burden and hide the meaningful diff.
+- Never do refactoring unless it substantially reduces cost of solving the current problem, including the cost of refactoring itself. Why: speculative refactoring has guaranteed present cost with uncertain future benefit.
+- Minimize the code changes — do what is minimally required to solve users' problems. Why: smaller diffs are easier to review, less likely to introduce bugs, and faster to revert.
+
+**Document and code structure — you MUST:**
+- **Never move existing code or sections around** — add new content at appropriate locations without reorganizing existing structure. Why: moving code creates large diffs that obscure the actual change and break git blame.
+- When adding new sections to documents, continue the existing numbering scheme. Why: consistent numbering preserves document navigability.
+- Minimize diff size — prefer small, targeted changes over reorganization. Why: large diffs compound review errors and make rollback difficult.
+
+**Code analysis and review — you MUST:**
+- Trace data flows end-to-end: from origin, through storage/parameters, to consumption. Flag values that are discarded and reconstructed from partial data (e.g. extracted from a URI missing original fields) — this is usually a bug. Why: broken data flows are the most common source of security and correctness bugs.
+- Read implementations of called functions, not just signatures — if duplication involves a called function, check whether decomposing it resolves the duplication. Why: function signatures can be misleading about actual behavior.
+- Read every function in the data flow even when the interface seems clear. Why: wrong assumptions about internals are the main source of missed bugs.
+
+---
+
+## Plans
+
+When developing via plans (non-trivial features, multi-step changes, architectural decisions), you MUST store the plan in the `plans/` folder before implementing. Why: plans are the persistent record of design decisions and rationale — without them, future sessions cannot understand why the system was built the way it was.
+
+### Plan requirements
+
+1. **File naming.** You MUST use the format `YYYYMMDD_NN.md` (e.g., `20260211_01.md`). Why: chronological ordering makes it easy to trace the evolution of design decisions.
+
+2. **Plan structure.** Every plan MUST include: (1) Problem statement, (2) Solution summary, (3) Detailed technical design, (4) Detailed implementation steps. Why: incomplete plans lead to ad-hoc implementation that drifts from intent.
+
+3. **Consistency with product/ and spec/.** The plan MUST be consistent with the current state of `product/` and `spec/`. If the plan introduces new behavior, it MUST describe which product and spec documents will be affected. Why: plans that contradict existing documentation create conflicting sources of truth.
+
+4. **Adversarial self-review.** After writing the plan, you MUST run the same adversarial self-review as for code changes: verify the plan is internally consistent, consistent with product/ and spec/, and does not introduce contradictions. You MUST repeat until two consecutive passes find zero issues. Why: an incoherent plan produces incoherent implementation.
+
+---
+
+## Change Protocol
+
+### The rule
+
+Every code change MUST include corresponding updates to `spec/` and `product/`. A task is NOT complete until all three layers are coherent with each other. Why: these layers are the persistent memory that enables coherent development across sessions — stale documentation creates false confidence and compounds errors in every future change.
+
+### What to update
+
+1. **spec/ — on every code change.** You MUST update the corresponding spec document to reflect the change. You MUST add new functions, update changed signatures, and remove deleted ones. Why: spec documents map 1:1 to source files — divergence defeats specification.
+
+2. **product/ — when user-visible behavior changes.** You MUST update the relevant `product/views/*.md` and any affected `product/flows/*.md`. You MUST update `product/rules.md` when business invariants change. Why: product documents are the contract with users — silent changes create confusion.
+
+3. **Line number references — on every code change.** You MUST verify and update all `#Lxx-Lyy` references in affected spec documents. Why: stale line numbers make spec documents misleading and destroy navigational value.
+
+4. **Cross-references — when adding or removing files.** You MUST add corresponding spec documents and update `spec/README.md` document index and reverse index. When adding pages, you MUST add `product/views/` and `spec/client/` documents. You MUST update the Document Map at the end of this file. Why: every source file must be covered for the navigation system to work.
+
+5. **Impact graph — when adding files or changing what a file affects.** You MUST update `spec/impact.md` to reflect the source file → product concept mapping. Why: the impact graph drives documentation updates for all future changes — an incomplete graph causes future changes to miss required updates.
+
+6. **Concept index — when adding or changing product concepts.** You MUST add or update the relevant row in `product/concepts.md` with links to product docs, spec docs, source files, and tests. Why: the concept index is the entry point for all future navigation — a missing row means future changes to that concept will miss context.
+
+7. **[GAP] annotations — when discovering issues.** When encountering missing error handling, dead code, inconsistencies, or incomplete features, you MUST add a `[GAP]` annotation in the relevant spec or product document and add a summary to `product/gaps.md`. Why: this builds institutional knowledge about technical debt.
+
+8. **[REC] annotations — when identifying improvements.** You MUST add a `[REC]` annotation in the relevant document. Why: capturing improvement ideas at discovery time preserves context that is lost later.
+
+9. **Preserve document structure.** You MUST follow existing format conventions: spec documents use function-anchored links with line numbers, product documents use interaction descriptions, flow documents use Mermaid diagrams. Why: consistent structure makes documents predictable and navigable.
+
+### Adversarial self-review
+
+After completing all changes (code + documentation), you MUST run an adversarial self-review. You MUST check coherence both within each layer and across layers.
+
+**Within-layer coherence — you MUST verify:**
+- spec/ is internally consistent — no contradictory descriptions, state machines have no unreachable states, data model is referentially intact
+- product/ is internally consistent — flows match views, rules match behavior descriptions
+
+**Across-layer coherence — you MUST verify:**
+- Every new or changed function in source appears in the corresponding spec/ document
+- Every user-visible behavior change in source appears in the relevant product/ document
+- All `#Lxx-Lyy` line references in affected spec documents point to the correct lines
+- All cross-references resolve — product → spec links, spec → source links
+- `spec/impact.md` covers all affected product concepts for the changed source files
+- `product/concepts.md` rows are current for any affected concepts
+
+**Convergence:** You MUST repeat the review-and-fix cycle until two consecutive passes find zero issues. You MUST fix all issues discovered between passes. Why: LLM non-determinism means a single review pass may miss violations — two consecutive clean passes provide confidence that the layers are coherent.
+
+---
+
+## Document Map
+
+### iOS Swift Sources
+
+| Source Location | Spec Document | Product Document |
+|----------------|---------------|-----------------|
+| Shared/ContentView.swift | spec/client/navigation.md | product/views/chat-list.md |
+| Shared/SimpleXApp.swift | spec/architecture.md | product/flows/onboarding.md |
+| Shared/AppDelegate.swift | spec/services/notifications.md | product/flows/onboarding.md |
+| Shared/Views/ChatList/ChatListView.swift | spec/client/chat-list.md | product/views/chat-list.md |
+| Shared/Views/Chat/ChatView.swift | spec/client/chat-view.md | product/views/chat.md |
+| Shared/Views/Chat/ComposeMessage/ComposeView.swift | spec/client/compose.md | product/views/chat.md |
+| Shared/Views/Chat/ChatItem/ | spec/client/chat-view.md | product/views/chat.md |
+| Shared/Views/Chat/ChatInfoView.swift | spec/client/chat-view.md | product/views/contact-info.md |
+| Shared/Views/Chat/Group/GroupChatInfoView.swift | spec/client/chat-view.md | product/views/group-info.md |
+| Shared/Views/Chat/Group/AddGroupMembersView.swift | spec/client/chat-view.md | product/views/group-info.md |
+| Shared/Views/Chat/Group/GroupLinkView.swift | spec/client/chat-view.md | product/views/group-info.md |
+| Shared/Views/Chat/Group/GroupMemberInfoView.swift | spec/client/chat-view.md | product/views/group-info.md |
+| Shared/Views/NewChat/NewChatView.swift | spec/client/navigation.md | product/views/new-chat.md |
+| Shared/Views/NewChat/QRCode.swift | spec/client/navigation.md | product/views/new-chat.md |
+| Shared/Views/Call/ActiveCallView.swift | spec/services/calls.md | product/views/call.md |
+| Shared/Views/Call/CallController.swift | spec/services/calls.md | product/flows/calling.md |
+| Shared/Views/Call/WebRTCClient.swift | spec/services/calls.md | product/flows/calling.md |
+| Shared/Views/UserSettings/SettingsView.swift | spec/client/navigation.md | product/views/settings.md |
+| Shared/Views/UserSettings/AppearanceSettings.swift | spec/services/theme.md | product/views/settings.md |
+| Shared/Views/UserSettings/NetworkAndServers/ | spec/architecture.md | product/views/settings.md |
+| Shared/Views/UserSettings/UserProfilesView.swift | spec/client/navigation.md | product/views/user-profiles.md |
+| Shared/Views/Onboarding/ | spec/client/navigation.md | product/views/onboarding.md |
+| Shared/Views/LocalAuth/ | spec/architecture.md | product/views/settings.md |
+| Shared/Views/Database/ | spec/database.md | product/views/settings.md |
+| Shared/Views/Migration/ | spec/database.md | product/flows/onboarding.md |
+| Shared/Model/ChatModel.swift | spec/state.md | product/concepts.md |
+| Shared/Model/SimpleXAPI.swift | spec/api.md, spec/architecture.md | product/concepts.md |
+| Shared/Model/AppAPITypes.swift | spec/api.md | product/concepts.md |
+| Shared/Model/NtfManager.swift | spec/services/notifications.md | product/flows/messaging.md |
+| Shared/Model/BGManager.swift | spec/services/notifications.md | product/flows/messaging.md |
+| Shared/Theme/ThemeManager.swift | spec/services/theme.md | product/views/settings.md |
+| SimpleXChat/ChatTypes.swift | spec/state.md, spec/api.md | product/glossary.md |
+| SimpleXChat/APITypes.swift | spec/api.md | product/concepts.md |
+| SimpleXChat/CallTypes.swift | spec/services/calls.md | product/flows/calling.md |
+| SimpleXChat/FileUtils.swift | spec/services/files.md | product/flows/file-transfer.md |
+| SimpleXChat/Notifications.swift | spec/services/notifications.md | product/flows/messaging.md |
+| SimpleX NSE/NotificationService.swift | spec/services/notifications.md | product/flows/messaging.md |
+
+### Haskell Core Sources (at `../../src/Simplex/Chat/` relative to `apps/ios/`)
+
+| Source Location | Spec Document | Product Document |
+|----------------|---------------|-----------------|
+| ../../src/Simplex/Chat/Controller.hs | spec/api.md | product/concepts.md |
+| ../../src/Simplex/Chat/Types.hs | spec/api.md | product/glossary.md |
+| ../../src/Simplex/Chat/Core.hs | spec/architecture.md | product/concepts.md |
+| ../../src/Simplex/Chat/Protocol.hs | spec/architecture.md | product/concepts.md |
+| ../../src/Simplex/Chat/Messages.hs | spec/api.md | product/flows/messaging.md |
+| ../../src/Simplex/Chat/Messages/CIContent.hs | spec/api.md | product/flows/messaging.md |
+| ../../src/Simplex/Chat/Call.hs | spec/services/calls.md | product/flows/calling.md |
+| ../../src/Simplex/Chat/Files.hs | spec/services/files.md | product/flows/file-transfer.md |
+| ../../src/Simplex/Chat/Store/Messages.hs | spec/database.md | product/flows/messaging.md |
+| ../../src/Simplex/Chat/Store/Groups.hs | spec/database.md | product/flows/group-lifecycle.md |
+| ../../src/Simplex/Chat/Store/Direct.hs | spec/database.md | product/flows/connection.md |
+| ../../src/Simplex/Chat/Store/Files.hs | spec/database.md | product/flows/file-transfer.md |
+| ../../src/Simplex/Chat/Store/Profiles.hs | spec/database.md | product/views/user-profiles.md |
diff --git a/apps/ios/README.md b/apps/ios/README.md
index de6c52c01d..fb6a6ed40d 100644
--- a/apps/ios/README.md
+++ b/apps/ios/README.md
@@ -1 +1,93 @@
# SimpleX Chat iOS app
+
+This file provides guidance when working with code in this repository.
+
+## iOS App Overview
+
+The iOS app is a SwiftUI application that interfaces with the Haskell core library via FFI. It shares the SimpleXChat framework with two extensions: Notification Service Extension (NSE) for push notifications and Share Extension (SE) for sharing content from other apps.
+
+## Build & Development
+
+Open `SimpleX.xcodeproj` in Xcode. The project has five targets:
+- **SimpleX (iOS)** - Main app (Bundle ID: `chat.simplex.app`)
+- **SimpleXChat** - Framework containing FFI bridge and shared types
+- **SimpleX NSE** - Notification Service Extension
+- **SimpleX SE** - Share Extension
+- **Tests iOS** - UI tests
+
+Build and run via Xcode (Product > Build/Run). Tests run via Product > Test or:
+```bash
+xcodebuild test -scheme "SimpleX (iOS)" -destination 'platform=iOS Simulator,name=iPhone 15'
+```
+
+Deployment target: iOS 15.0+, Swift 5.0.
+
+## Architecture
+
+### Haskell Core Integration
+
+The app calls the Haskell core library through C FFI defined in `SimpleXChat/SimpleX.h`:
+- `chat_migrate_init_key()` - Initialize/migrate database
+- `chat_send_cmd_retry()` - Send command to chat controller
+- `chat_recv_msg_wait()` - Receive messages from controller
+
+Swift wrappers in `SimpleXChat/API.swift`:
+- `chatMigrateInit()` - Initialize chat controller
+- `sendSimpleXCmd()` - Send typed commands and parse responses
+- `recvSimpleXMsg()` - Receive typed messages
+
+Haskell runtime initialization (`SimpleXChat/hs_init.c`) uses different memory configurations:
+- Main app: 64MB heap
+- NSE: 512KB heap (minimal footprint for background processing)
+- SE: 1MB heap
+
+Pre-compiled Haskell libraries are in `Libraries/{ios,mac,sim}/`.
+
+### State Management
+
+- **ChatModel** (`Shared/Model/ChatModel.swift`) - Main singleton `ObservableObject` for app-wide state (chat list, active chat, users)
+- **ItemsModel** - Manages chat items within a selected chat (similar to Kotlin's ChatsContext)
+- **AppTheme** - Theme management and customization
+
+### App Structure
+
+Entry point: `Shared/SimpleXApp.swift`
+
+Key directories in `Shared/`:
+- `Model/` - Data models and API layer (`ChatModel.swift`, `SimpleXAPI.swift`)
+- `Views/` - SwiftUI views organized by feature:
+ - `ChatList/` - Chat list and user picker
+ - `Chat/` - Message display and composition
+ - `Call/` - VoIP call UI
+ - `UserSettings/` - App settings
+ - `LocalAuth/` - Passcode and biometric authentication
+ - `Database/` - Database initialization and migration
+
+### Shared Data Between Targets
+
+All three targets share data via App Group (`group.chat.simplex.app`):
+- `SimpleXChat/AppGroup.swift` - GroupDefaults wrapper for typed shared preferences
+- Keychain for sensitive data: `kcDatabasePassword`, `kcAppPassword`, `kcSelfDestructPassword`
+
+### Key Types
+
+Types are defined in `SimpleXChat/`:
+- `ChatTypes.swift` - User, Chat, Message, Group types
+- `APITypes.swift` - API request/response types
+
+Commands follow `ChatCmdProtocol` (has `cmdString` property), sent as JSON through FFI.
+
+## Localization
+
+31 languages supported. Localization files in `SimpleX Localizations/`.
+
+Workflow:
+- `Product > Export Localizations` - Export XLIFF files
+- `Product > Import Localizations` - Import updated translations
+
+## Background Capabilities
+
+Configured in Info.plist:
+- Background modes: audio, fetch, remote-notification, voip
+- URL scheme: `simplex://` for deep linking
+- BGTaskScheduler: `chat.simplex.app.receive`
diff --git a/apps/ios/Shared/AppDelegate.swift b/apps/ios/Shared/AppDelegate.swift
index 3f6998c9ec..0a401f9bf3 100644
--- a/apps/ios/Shared/AppDelegate.swift
+++ b/apps/ios/Shared/AppDelegate.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 30/03/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/services/notifications.md
import Foundation
import UIKit
diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift
index 7adf7a0435..a6896fa51d 100644
--- a/apps/ios/Shared/ContentView.swift
+++ b/apps/ios/Shared/ContentView.swift
@@ -4,6 +4,7 @@
//
// Created by Evgeny Poberezkin on 17/01/2022.
//
+// Spec: spec/client/navigation.md
import SwiftUI
import Intents
@@ -19,15 +20,18 @@ private enum NoticesSheet: Identifiable {
}
}
+// Spec: spec/client/navigation.md#ContentView
struct ContentView: View {
@EnvironmentObject var chatModel: ChatModel
@ObservedObject var alertManager = AlertManager.shared
@ObservedObject var callController = CallController.shared
+ // Spec: spec/client/navigation.md#AppSheetState
@ObservedObject var appSheetState = AppSheetState.shared
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var theme: AppTheme
@EnvironmentObject var sceneDelegate: SceneDelegate
+ // Spec: spec/client/navigation.md#contentAccessAuthenticationExtended
var contentAccessAuthenticationExtended: Bool
@Environment(\.scenePhase) var scenePhase
@@ -161,6 +165,7 @@ struct ContentView: View {
}
}
+ // Spec: spec/client/navigation.md#contentView
@ViewBuilder private func contentView() -> some View {
if let status = chatModel.chatDbStatus, status != .ok {
DatabaseErrorView(status: status)
@@ -176,6 +181,7 @@ struct ContentView: View {
}
}
+ // Spec: spec/client/navigation.md#callView
@ViewBuilder private func callView(_ call: Call) -> some View {
if CallController.useCallKit() {
ActiveCallView(call: call, canConnectCall: Binding.constant(true))
@@ -193,6 +199,7 @@ struct ContentView: View {
}
}
+ // Spec: spec/client/navigation.md#callBanner
private func activeCallInteractiveArea(_ call: Call) -> some View {
HStack {
Text(call.contact.displayName).font(.body).foregroundColor(.white)
@@ -227,6 +234,7 @@ struct ContentView: View {
}
}
+ // Spec: spec/client/navigation.md#lockButton
private func lockButton() -> some View {
Button(action: authenticateContentViewAccess) { Label("Unlock", systemImage: "lock") }
}
@@ -339,6 +347,7 @@ struct ContentView: View {
}
}
+ // Spec: spec/client/navigation.md#unlockedRecently
private func unlockedRecently() -> Bool {
if let lastSuccessfulUnlock = lastSuccessfulUnlock {
return ProcessInfo.processInfo.systemUptime - lastSuccessfulUnlock < 2
@@ -426,6 +435,7 @@ struct ContentView: View {
)
}
+ // Spec: spec/client/navigation.md#connectViaUrl
func connectViaUrl() {
let m = ChatModel.shared
if let url = m.appOpenUrl {
diff --git a/apps/ios/Shared/Model/AppAPITypes.swift b/apps/ios/Shared/Model/AppAPITypes.swift
index 193b675a57..f82a2fd2eb 100644
--- a/apps/ios/Shared/Model/AppAPITypes.swift
+++ b/apps/ios/Shared/Model/AppAPITypes.swift
@@ -5,11 +5,13 @@
// Created by EP on 01/05/2025.
// Copyright © 2025 SimpleX Chat. All rights reserved.
//
+// Spec: spec/api.md
import SimpleXChat
import SwiftUI
// some constructors are used in SEChatCommand or NSEChatCommand types as well - they must be syncronised
+// Spec: spec/api.md#ChatCommand
enum ChatCommand: ChatCmdProtocol {
case showActiveUser
case createActiveUser(profile: Profile?, pastTimestamp: Bool)
@@ -41,6 +43,7 @@ enum ChatCommand: ChatCmdProtocol {
case apiGetChatTags(userId: Int64)
case apiGetChats(userId: Int64)
case apiGetChat(chatId: ChatId, scope: GroupChatScope?, contentTag: MsgContentTag?, pagination: ChatPagination, search: String)
+ case apiGetChatContentTypes(chatId: ChatId, scope: GroupChatScope?)
case apiGetChatItemInfo(type: ChatType, id: Int64, scope: GroupChatScope?, itemId: Int64)
case apiSendMessages(type: ChatType, id: Int64, scope: GroupChatScope?, live: Bool, ttl: Int?, composedMessages: [ComposedMessage])
case apiCreateChatTag(tag: ChatTagData)
@@ -224,7 +227,8 @@ enum ChatCommand: ChatCmdProtocol {
case let .apiGetChats(userId): return "/_get chats \(userId) pcc=on"
case let .apiGetChat(chatId, scope, contentTag, pagination, search):
let tag = contentTag != nil ? " content=\(contentTag!.rawValue)" : ""
- return "/_get chat \(chatId)\(scopeRef(scope: scope))\(tag) \(pagination.cmdString)" + (search == "" ? "" : " search=\(search)")
+ return "/_get chat \(chatId)\(scopeRef(scope))\(tag) \(pagination.cmdString)" + (search == "" ? "" : " search=\(search)")
+ case let .apiGetChatContentTypes(chatId, scope): return "/_get content types \(chatId)\(scopeRef(scope))"
case let .apiGetChatItemInfo(type, id, scope, itemId): return "/_get item info \(ref(type, id, scope: scope)) \(itemId)"
case let .apiSendMessages(type, id, scope, live, ttl, composedMessages):
let msgs = encodeJSON(composedMessages)
@@ -417,6 +421,7 @@ enum ChatCommand: ChatCmdProtocol {
case .apiGetChatTags: return "apiGetChatTags"
case .apiGetChats: return "apiGetChats"
case .apiGetChat: return "apiGetChat"
+ case .apiGetChatContentTypes: return "apiGetChatContentTypes"
case .apiGetChatItemInfo: return "apiGetChatItemInfo"
case .apiSendMessages: return "apiSendMessages"
case .apiCreateChatTag: return "apiCreateChatTag"
@@ -559,10 +564,10 @@ enum ChatCommand: ChatCmdProtocol {
}
func ref(_ type: ChatType, _ id: Int64, scope: GroupChatScope?) -> String {
- "\(type.rawValue)\(id)\(scopeRef(scope: scope))"
+ "\(type.rawValue)\(id)\(scopeRef(scope))"
}
- func scopeRef(scope: GroupChatScope?) -> String {
+ func scopeRef(_ scope: GroupChatScope?) -> String {
switch (scope) {
case .none: ""
case let .memberSupport(groupMemberId_):
@@ -640,6 +645,7 @@ enum ChatCommand: ChatCmdProtocol {
}
// ChatResponse is split to three enums to reduce stack size used when parsing it, parsing large enums is very inefficient.
+// Spec: spec/api.md#ChatResponse0
enum ChatResponse0: Decodable, ChatAPIResult {
case activeUser(user: User)
case usersList(users: [UserInfo])
@@ -648,6 +654,7 @@ enum ChatResponse0: Decodable, ChatAPIResult {
case chatStopped
case apiChats(user: UserRef, chats: [ChatData])
case apiChat(user: UserRef, chat: ChatData, navInfo: NavigationInfo?)
+ case chatContentTypes(contentTypes: [MsgContentTag])
case chatTags(user: UserRef, userTags: [ChatTag])
case chatItemInfo(user: UserRef, chatItem: AChatItem, chatItemInfo: ChatItemInfo)
case serverTestResult(user: UserRef, testServer: String, testFailure: ProtocolTestFailure?)
@@ -680,6 +687,7 @@ enum ChatResponse0: Decodable, ChatAPIResult {
case .chatStopped: "chatStopped"
case .apiChats: "apiChats"
case .apiChat: "apiChat"
+ case .chatContentTypes: "chatContentTypes"
case .chatTags: "chatTags"
case .chatItemInfo: "chatItemInfo"
case .serverTestResult: "serverTestResult"
@@ -714,6 +722,7 @@ enum ChatResponse0: Decodable, ChatAPIResult {
case .chatStopped: return noDetails
case let .apiChats(u, chats): return withUser(u, String(describing: chats))
case let .apiChat(u, chat, navInfo): return withUser(u, "chat: \(String(describing: chat))\nnavInfo: \(String(describing: navInfo))")
+ case let .chatContentTypes(types): return "content types: \(String(describing: types))"
case let .chatTags(u, userTags): return withUser(u, "userTags: \(String(describing: userTags))")
case let .chatItemInfo(u, chatItem, chatItemInfo): return withUser(u, "chatItem: \(String(describing: chatItem))\nchatItemInfo: \(String(describing: chatItemInfo))")
case let .serverTestResult(u, server, testFailure): return withUser(u, "server: \(server)\nresult: \(String(describing: testFailure))")
@@ -758,6 +767,7 @@ enum ChatResponse0: Decodable, ChatAPIResult {
}
}
+// Spec: spec/api.md#ChatResponse1
enum ChatResponse1: Decodable, ChatAPIResult {
case invitation(user: UserRef, connLinkInvitation: CreatedConnLink, connection: PendingContactConnection)
case connectionIncognitoUpdated(user: UserRef, toConnection: PendingContactConnection)
@@ -897,6 +907,7 @@ enum ChatResponse1: Decodable, ChatAPIResult {
}
}
+// Spec: spec/api.md#ChatResponse2
enum ChatResponse2: Decodable, ChatAPIResult {
// group responses
case groupCreated(user: UserRef, groupInfo: GroupInfo)
@@ -1040,6 +1051,7 @@ enum ChatResponse2: Decodable, ChatAPIResult {
}
}
+// Spec: spec/api.md#ChatEvent
enum ChatEvent: Decodable, ChatAPIResult {
case chatSuspended
case contactSwitch(user: UserRef, contact: Contact, switchProgress: SwitchProgress)
diff --git a/apps/ios/Shared/Model/BGManager.swift b/apps/ios/Shared/Model/BGManager.swift
index 25eab6c69e..aa4dfa24f8 100644
--- a/apps/ios/Shared/Model/BGManager.swift
+++ b/apps/ios/Shared/Model/BGManager.swift
@@ -5,6 +5,7 @@
// Created by Evgeny Poberezkin on 08/02/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/services/notifications.md
import Foundation
import BackgroundTasks
@@ -25,6 +26,7 @@ private let maxBgRefreshInterval: TimeInterval = 2400 // 40 minutes
private let maxTimerCount = 9
+// Spec: spec/services/notifications.md#BGManager
class BGManager {
static let shared = BGManager()
var chatReceiver: ChatReceiver?
@@ -32,6 +34,7 @@ class BGManager {
var completed = true
var timerCount = 0
+ // Spec: spec/services/notifications.md#register
func register() {
logger.debug("BGManager.register")
BGTaskScheduler.shared.register(forTaskWithIdentifier: receiveTaskId, using: nil) { task in
@@ -39,6 +42,7 @@ class BGManager {
}
}
+ // Spec: spec/services/notifications.md#schedule
func schedule(interval: TimeInterval? = nil) {
if !ChatModel.shared.ntfEnableLocal {
logger.debug("BGManager.schedule: disabled")
@@ -66,6 +70,7 @@ class BGManager {
Date.now.timeIntervalSince(chatLastBackgroundRunGroupDefault.get()) > runInterval
}
+ // Spec: spec/services/notifications.md#handleRefresh
private func handleRefresh(_ task: BGAppRefreshTask) {
if !ChatModel.shared.ntfEnableLocal {
logger.debug("BGManager.handleRefresh: disabled")
@@ -103,6 +108,7 @@ class BGManager {
}
}
+ // Spec: spec/services/notifications.md#receiveMessages-BG
func receiveMessages(_ completeReceiving: @escaping (String) -> Void) {
if (!self.completed) {
logger.debug("BGManager.receiveMessages: in progress, exiting")
diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift
index f1f4e686bd..46e9df1ef8 100644
--- a/apps/ios/Shared/Model/ChatModel.swift
+++ b/apps/ios/Shared/Model/ChatModel.swift
@@ -5,6 +5,7 @@
// Created by Evgeny Poberezkin on 22/01/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/state.md
import Foundation
import Combine
@@ -53,6 +54,7 @@ private func addTermItem(_ items: inout [TerminalItem], _ item: TerminalItem) {
}
// analogue for SecondaryContextFilter in Kotlin
+// Spec: spec/state.md#SecondaryItemsModelFilter
enum SecondaryItemsModelFilter {
case groupChatScopeContext(groupScopeInfo: GroupChatScopeInfo)
case msgContentTagContext(contentTag: MsgContentTag)
@@ -68,6 +70,7 @@ enum SecondaryItemsModelFilter {
}
// analogue for ChatsContext in Kotlin
+// Spec: spec/state.md#ItemsModel
class ItemsModel: ObservableObject {
static let shared = ItemsModel(secondaryIMFilter: nil)
public var secondaryIMFilter: SecondaryItemsModelFilter?
@@ -103,12 +106,14 @@ class ItemsModel: ObservableObject {
.store(in: &bag)
}
+ // Spec: spec/state.md#loadSecondaryChat
static func loadSecondaryChat(_ chatId: ChatId, chatFilter: SecondaryItemsModelFilter, willNavigate: @escaping () -> Void = {}) {
let im = ItemsModel(secondaryIMFilter: chatFilter)
ChatModel.shared.secondaryIM = im
im.loadOpenChat(chatId, willNavigate: willNavigate)
}
+ // Spec: spec/state.md#loadOpenChat
func loadOpenChat(_ chatId: ChatId, willNavigate: @escaping () -> Void = {}) {
navigationTimeoutTask?.cancel()
loadChatTask?.cancel()
@@ -134,6 +139,7 @@ class ItemsModel: ObservableObject {
}
}
+ // Spec: spec/state.md#loadOpenChatNoWait
func loadOpenChatNoWait(_ chatId: ChatId, _ openAroundItemId: ChatItem.ID? = nil) {
navigationTimeoutTask?.cancel()
loadChatTask?.cancel()
@@ -179,6 +185,7 @@ class PreloadState {
}
}
+// Spec: spec/state.md#ChatTagsModel
class ChatTagsModel: ObservableObject {
static let shared = ChatTagsModel()
@@ -326,6 +333,7 @@ class ConnectProgressManager: ObservableObject {
}
}
+// Spec: spec/state.md#ChatModel
final class ChatModel: ObservableObject {
@Published var onboardingStage: OnboardingStage?
@Published var setDeliveryReceipts = false
@@ -383,6 +391,7 @@ final class ChatModel: ObservableObject {
@Published var showCallView = false
@Published var activeCallViewIsCollapsed = false
// remote desktop
+ // Spec: spec/architecture.md#remoteCtrlSession
@Published var remoteCtrlSession: RemoteCtrlSession?
// currently showing invitation
@Published var showingInvitation: ShowingInvitation?
@@ -423,6 +432,7 @@ final class ChatModel: ObservableObject {
userAddress?.shortLinkDataSet ?? true
}
+ // Spec: spec/state.md#getUser
func getUser(_ userId: Int64) -> User? {
currentUser?.userId == userId
? currentUser
@@ -433,6 +443,7 @@ final class ChatModel: ObservableObject {
users.firstIndex { $0.user.userId == user.userId }
}
+ // Spec: spec/state.md#updateUser
func updateUser(_ user: User) {
if let i = getUserIndex(user) {
users[i].user = user
@@ -442,6 +453,7 @@ final class ChatModel: ObservableObject {
}
}
+ // Spec: spec/state.md#removeUser
func removeUser(_ user: User) {
if let i = getUserIndex(user) {
users.remove(at: i)
@@ -452,6 +464,7 @@ final class ChatModel: ObservableObject {
chats.first(where: { $0.id == id }) != nil
}
+ // Spec: spec/state.md#getChat
func getChat(_ id: String) -> Chat? {
chats.first(where: { $0.id == id })
}
@@ -506,6 +519,7 @@ final class ChatModel: ObservableObject {
chats.firstIndex(where: { $0.id == id })
}
+ // Spec: spec/state.md#addChat
func addChat(_ chat: Chat) {
if chatId == nil {
withAnimation { addChat_(chat, at: 0) }
@@ -519,6 +533,7 @@ final class ChatModel: ObservableObject {
chats.insert(chat, at: position)
}
+ // Spec: spec/state.md#updateChatInfo
func updateChatInfo(_ cInfo: ChatInfo) {
if let i = getChatIndex(cInfo.id) {
if case let .group(groupInfo, groupChatScope) = cInfo, groupChatScope != nil {
@@ -570,6 +585,7 @@ final class ChatModel: ObservableObject {
}
}
+ // Spec: spec/state.md#replaceChat
func replaceChat(_ id: String, _ chat: Chat) {
if let i = getChatIndex(id) {
chats[i] = chat
@@ -1054,6 +1070,7 @@ final class ChatModel: ObservableObject {
NtfManager.shared.changeNtfBadgeCount(by: by)
}
+ // Spec: spec/state.md#totalUnreadCountForAllUsers
func totalUnreadCountForAllUsers() -> Int {
var unread: Int = 0
for chat in chats {
@@ -1153,6 +1170,7 @@ final class ChatModel: ObservableObject {
return (prevMember, memberIds.count)
}
+ // Spec: spec/state.md#popChat
func popChat(_ id: String) {
if let i = getChatIndex(id) {
// no animation here, for it not to look like it just moved when leaving the chat
@@ -1176,6 +1194,7 @@ final class ChatModel: ObservableObject {
showingInvitation?.connChatUsed = true
}
+ // Spec: spec/state.md#removeChat
func removeChat(_ id: String) {
withAnimation {
if let i = getChatIndex(id) {
@@ -1248,6 +1267,7 @@ struct NTFContactRequest {
var chatId: String
}
+// Spec: spec/state.md#Chat
final class Chat: ObservableObject, Identifiable, ChatLike {
@Published var chatInfo: ChatInfo
@Published var chatItems: [ChatItem]
diff --git a/apps/ios/Shared/Model/NtfManager.swift b/apps/ios/Shared/Model/NtfManager.swift
index 79f4ef2f09..c6c6e88d8c 100644
--- a/apps/ios/Shared/Model/NtfManager.swift
+++ b/apps/ios/Shared/Model/NtfManager.swift
@@ -5,6 +5,7 @@
// Created by Evgeny Poberezkin on 08/02/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/services/notifications.md
import Foundation
import UserNotifications
@@ -22,6 +23,7 @@ enum NtfCallAction {
case reject
}
+// Spec: spec/services/notifications.md#NtfManager
class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
static let shared = NtfManager()
@@ -48,6 +50,7 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
handler()
}
+ // Spec: spec/services/notifications.md#processNotificationResponse
func processNotificationResponse(_ ntfResponse: UNNotificationResponse) {
let chatModel = ChatModel.shared
let content = ntfResponse.notification.request.content
@@ -149,6 +152,7 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
return false
}
+ // Spec: spec/services/notifications.md#registerCategories
func registerCategories() {
logger.debug("NtfManager.registerCategories")
UNUserNotificationCenter.current().setNotificationCategories([
@@ -207,6 +211,7 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
])
}
+ // Spec: spec/services/notifications.md#requestAuthorization
func requestAuthorization(onDeny denied: (()-> Void)? = nil, onAuthorized authorized: (()-> Void)? = nil) {
logger.debug("NtfManager.requestAuthorization")
let center = UNUserNotificationCenter.current()
@@ -230,6 +235,7 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
}
}
+ // Spec: spec/services/notifications.md#notifyContactRequest
func notifyContactRequest(_ user: any UserLike, _ contactRequest: UserContactRequest) {
logger.debug("NtfManager.notifyContactRequest")
addNotification(createContactRequestNtf(user, contactRequest, 0))
@@ -240,6 +246,7 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
addNotification(createContactConnectedNtf(user, contact, 0))
}
+ // Spec: spec/services/notifications.md#notifyMessageReceived
func notifyMessageReceived(_ user: any UserLike, _ cInfo: ChatInfo, _ cItem: ChatItem) {
logger.debug("NtfManager.notifyMessageReceived")
if cInfo.ntfsEnabled(chatItem: cItem) {
@@ -247,16 +254,19 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
}
}
+ // Spec: spec/services/notifications.md#notifyCallInvitation
func notifyCallInvitation(_ invitation: RcvCallInvitation) {
logger.debug("NtfManager.notifyCallInvitation")
addNotification(createCallInvitationNtf(invitation, 0))
}
+ // Spec: spec/services/notifications.md#setNtfBadgeCount
func setNtfBadgeCount(_ count: Int) {
UIApplication.shared.applicationIconBadgeNumber = count
ntfBadgeCountGroupDefault.set(count)
}
+ // Spec: spec/services/notifications.md#changeNtfBadgeCount
func changeNtfBadgeCount(by count: Int = 1) {
setNtfBadgeCount(max(0, UIApplication.shared.applicationIconBadgeNumber + count))
}
diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift
index 5a042a6252..7eb2de11ab 100644
--- a/apps/ios/Shared/Model/SimpleXAPI.swift
+++ b/apps/ios/Shared/Model/SimpleXAPI.swift
@@ -5,6 +5,7 @@
// Created by Evgeny Poberezkin on 27/01/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/api.md | spec/architecture.md
import Foundation
import UIKit
@@ -49,6 +50,7 @@ enum TerminalItem: Identifiable {
}
}
+// Spec: spec/architecture.md#beginBGTask
func beginBGTask(_ handler: (() -> Void)? = nil) -> (() -> Void) {
var id: UIBackgroundTaskIdentifier!
var running = true
@@ -86,12 +88,14 @@ private func withBGTask(bgDelay: Double? = nil, f: @escaping () -> T) -> T {
return r
}
+// Spec: spec/api.md#chatSendCmdSync
@inline(__always)
func chatSendCmdSync(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, ctrl: chat_ctrl? = nil, log: Bool = true) throws -> R {
let res: APIResult = chatApiSendCmdSync(cmd, bgTask: bgTask, bgDelay: bgDelay, ctrl: ctrl, log: log)
return try apiResult(res)
}
+// Spec: spec/api.md#chatApiSendCmdSync
func chatApiSendCmdSync(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, ctrl: chat_ctrl? = nil, retryNum: Int32 = 0, log: Bool = true) -> APIResult {
if log {
logger.debug("chatSendCmd \(cmd.cmdType)")
@@ -112,12 +116,14 @@ func chatApiSendCmdSync(_ cmd: ChatCommand, bgTask: Bool = tru
return resp
}
+// Spec: spec/api.md#chatSendCmd
@inline(__always)
func chatSendCmd(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, ctrl: chat_ctrl? = nil, log: Bool = true) async throws -> R {
let res: APIResult = await chatApiSendCmd(cmd, bgTask: bgTask, bgDelay: bgDelay, ctrl: ctrl, log: log)
return try apiResult(res)
}
+// Spec: spec/api.md#chatApiSendCmdWithRetry
func chatApiSendCmdWithRetry(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, inProgress: BoxedValue? = nil, retryNum: Int32 = 0) async -> APIResult? {
let r: APIResult = await chatApiSendCmd(cmd, bgTask: bgTask, bgDelay: bgDelay, retryNum: retryNum)
if inProgress == nil || inProgress?.boxedValue == true,
@@ -210,6 +216,7 @@ func proxyDestinationErrorAlertMessage(proxyServer: String, destServer: String)
String.localizedStringWithFormat(NSLocalizedString("Forwarding server %@ failed to connect to destination server %@. Please try later.", comment: "alert message"), serverHostname(proxyServer), serverHostname(destServer))
}
+// Spec: spec/api.md#chatApiSendCmd
@inline(__always)
func chatApiSendCmd(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, ctrl: chat_ctrl? = nil, retryNum: Int32 = 0, log: Bool = true) async -> APIResult {
await withCheckedContinuation { cont in
@@ -226,6 +233,7 @@ func apiResult(_ res: APIResult) throws -> R {
}
}
+// Spec: spec/api.md#chatRecvMsg
func chatRecvMsg(_ ctrl: chat_ctrl? = nil) async -> APIResult? {
await withCheckedContinuation { cont in
_ = withBGTask(bgDelay: msgDelay) { () -> APIResult? in
@@ -346,6 +354,7 @@ func apiStopChat() async throws {
}
}
+// Spec: spec/architecture.md#apiActivateChat
func apiActivateChat() {
chatReopenStore()
do {
@@ -355,6 +364,7 @@ func apiActivateChat() {
}
}
+// Spec: spec/architecture.md#apiSuspendChat
func apiSuspendChat(timeoutMicroseconds: Int) {
do {
try sendCommandOkRespSync(.apiSuspendChat(timeoutMicroseconds: timeoutMicroseconds))
@@ -363,12 +373,14 @@ func apiSuspendChat(timeoutMicroseconds: Int) {
}
}
+// Spec: spec/services/files.md#apiSetAppFilePaths
func apiSetAppFilePaths(filesFolder: String, tempFolder: String, assetsFolder: String, ctrl: chat_ctrl? = nil) throws {
let r: ChatResponse2 = try chatSendCmdSync(.apiSetAppFilePaths(filesFolder: filesFolder, tempFolder: tempFolder, assetsFolder: assetsFolder), ctrl: ctrl)
if case .cmdOk = r { return }
throw r.unexpected
}
+// Spec: spec/services/files.md#apiSetEncryptLocalFiles
func apiSetEncryptLocalFiles(_ enable: Bool) throws {
try sendCommandOkRespSync(.apiSetEncryptLocalFiles(enable: enable))
}
@@ -444,11 +456,17 @@ func apiGetChat(chatId: ChatId, scope: GroupChatScope?, contentTag: MsgContentTa
throw r.unexpected
}
-func loadChat(chat: Chat, im: ItemsModel, search: String = "", clearItems: Bool = true) async {
- await loadChat(chatId: chat.chatInfo.id, im: im, search: search, clearItems: clearItems)
+func apiGetChatContentTypes(chatId: ChatId, scope: GroupChatScope? = nil) async throws -> [MsgContentTag] {
+ let r: ChatResponse0 = try await chatSendCmd(.apiGetChatContentTypes(chatId: chatId, scope: scope))
+ if case let .chatContentTypes(types) = r { return types }
+ throw r.unexpected
}
-func loadChat(chatId: ChatId, im: ItemsModel, search: String = "", openAroundItemId: ChatItem.ID? = nil, clearItems: Bool = true) async {
+func loadChat(chat: Chat, im: ItemsModel, contentTag: MsgContentTag? = nil, search: String = "", clearItems: Bool = true) async {
+ await loadChat(chatId: chat.chatInfo.id, im: im, contentTag: contentTag, search: search, clearItems: clearItems)
+}
+
+func loadChat(chatId: ChatId, im: ItemsModel, contentTag: MsgContentTag? = nil, search: String = "", openAroundItemId: ChatItem.ID? = nil, clearItems: Bool = true) async {
await MainActor.run {
if clearItems {
im.reversedChatItems = []
@@ -462,10 +480,11 @@ func loadChat(chatId: ChatId, im: ItemsModel, search: String = "", openAroundIte
openAroundItemId != nil
? .around(chatItemId: openAroundItemId!, count: loadItemsPerPage)
: (
- search == ""
+ contentTag == nil && search == ""
? .initial(count: loadItemsPerPage) : .last(count: loadItemsPerPage)
)
),
+ contentTag,
search,
openAroundItemId,
{ 0...0 }
@@ -1448,6 +1467,7 @@ func standaloneFileInfo(url: String, ctrl: chat_ctrl? = nil) async -> MigrationF
}
}
+// Spec: spec/services/files.md#receiveFile
func receiveFile(user: any UserLike, fileId: Int64, userApprovedRelays: Bool = false, auto: Bool = false) async {
await receiveFiles(
user: user,
@@ -1566,6 +1586,7 @@ func receiveFiles(user: any UserLike, fileIds: [Int64], userApprovedRelays: Bool
}
}
+// Spec: spec/services/files.md#cancelFile
func cancelFile(user: User, fileId: Int64) async {
if let chatItem = await apiCancelFile(fileId: fileId) {
await chatItemSimpleUpdate(user, chatItem)
@@ -1588,12 +1609,14 @@ func setLocalDeviceName(_ displayName: String) throws {
try sendCommandOkRespSync(.setLocalDeviceName(displayName: displayName))
}
+// Spec: spec/architecture.md#connectRemoteCtrl
func connectRemoteCtrl(desktopAddress: String) async throws -> (RemoteCtrlInfo?, CtrlAppInfo, String) {
let r: ChatResponse2 = try await chatSendCmd(.connectRemoteCtrl(xrcpInvitation: desktopAddress))
if case let .remoteCtrlConnecting(rc_, ctrlAppInfo, v) = r { return (rc_, ctrlAppInfo, v) }
throw r.unexpected
}
+// Spec: spec/architecture.md#findKnownRemoteCtrl
func findKnownRemoteCtrl() async throws {
try await sendCommandOkResp(.findKnownRemoteCtrl)
}
@@ -2071,6 +2094,7 @@ private func chatInitialized(start: Bool, refreshInvitations: Bool) throws {
}
}
+// Spec: spec/architecture.md#startChat
func startChat(refreshInvitations: Bool = true, onboarding: Bool = false) throws {
logger.debug("startChat")
let m = ChatModel.shared
@@ -2192,6 +2216,7 @@ private func getUserChatDataAsync(keepingChatId: String?) async throws {
}
}
+// Spec: spec/architecture.md#ChatReceiver
class ChatReceiver {
private var receiveLoop: Task?
private var receiveMessages = true
@@ -2237,6 +2262,7 @@ class ChatReceiver {
}
}
+// Spec: spec/api.md#processReceivedMsg
func processReceivedMsg(_ res: ChatEvent) async {
let m = ChatModel.shared
logger.debug("processReceivedMsg: \(res.responseType)")
diff --git a/apps/ios/Shared/SimpleXApp.swift b/apps/ios/Shared/SimpleXApp.swift
index e1a6bb61e8..1e9a97c31b 100644
--- a/apps/ios/Shared/SimpleXApp.swift
+++ b/apps/ios/Shared/SimpleXApp.swift
@@ -4,6 +4,7 @@
//
// Created by Evgeny Poberezkin on 17/01/2022.
//
+// Spec: spec/architecture.md
import SwiftUI
import OSLog
@@ -12,6 +13,7 @@ import SimpleXChat
let logger = Logger()
@main
+// Spec: spec/architecture.md#SimpleXApp
struct SimpleXApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@StateObject private var chatModel = ChatModel.shared
@@ -60,6 +62,7 @@ struct SimpleXApp: App {
}
}
}
+// Spec: spec/architecture.md#scenePhaseHandling
.onChange(of: scenePhase) { phase in
logger.debug("scenePhase was \(String(describing: scenePhase)), now \(String(describing: phase))")
AppSheetState.shared.scenePhaseActive = phase == .active
diff --git a/apps/ios/Shared/Theme/Theme.swift b/apps/ios/Shared/Theme/Theme.swift
index 3bd8f00c25..1f98b23a1d 100644
--- a/apps/ios/Shared/Theme/Theme.swift
+++ b/apps/ios/Shared/Theme/Theme.swift
@@ -10,6 +10,7 @@ import Foundation
import SwiftUI
import SimpleXChat
+// Spec: spec/services/theme.md#CurrentColors
var CurrentColors: ThemeManager.ActiveTheme = ThemeManager.currentColors(nil, nil, ChatModel.shared.currentUser?.uiThemes, themeOverridesDefault.get())
var MenuTextColor: Color { if isInDarkTheme() { AppTheme.shared.colors.onBackground.opacity(0.8) } else { Color.black } }
@@ -17,6 +18,7 @@ var NoteFolderIconColor: Color { AppTheme.shared.appColors.primaryVariant2 }
func isInDarkTheme() -> Bool { !CurrentColors.colors.isLight }
+// Spec: spec/services/theme.md#AppTheme
class AppTheme: ObservableObject, Equatable {
static let shared = AppTheme(name: CurrentColors.name, base: CurrentColors.base, colors: CurrentColors.colors, appColors: CurrentColors.appColors, wallpaper: CurrentColors.wallpaper)
@@ -89,6 +91,7 @@ struct ThemedBackground: ViewModifier {
}
}
+// Spec: spec/services/theme.md#systemInDarkThemeCurrently
var systemInDarkThemeCurrently: Bool {
return UITraitCollection.current.userInterfaceStyle == .dark
}
diff --git a/apps/ios/Shared/Theme/ThemeManager.swift b/apps/ios/Shared/Theme/ThemeManager.swift
index 4166619d04..b9a35163cf 100644
--- a/apps/ios/Shared/Theme/ThemeManager.swift
+++ b/apps/ios/Shared/Theme/ThemeManager.swift
@@ -5,12 +5,15 @@
// Created by Avently on 03.06.2024.
// Copyright © 2024 SimpleX Chat. All rights reserved.
//
+// Spec: spec/services/theme.md
import Foundation
import SwiftUI
import SimpleXChat
+// Spec: spec/services/theme.md#ThemeManager
class ThemeManager {
+ // Spec: spec/services/theme.md#ActiveTheme
struct ActiveTheme: Equatable {
let name: String
let base: DefaultTheme
@@ -41,6 +44,7 @@ class ThemeManager {
}
}
+ // Spec: spec/services/theme.md#defaultActiveTheme
static func defaultActiveTheme(_ appSettingsTheme: [ThemeOverrides]) -> ThemeOverrides? {
let nonSystemThemeName = nonSystemThemeName()
let defaultThemeId = currentThemeIdsDefault.get()[nonSystemThemeName]
@@ -56,6 +60,7 @@ class ThemeManager {
return ThemeModeOverride(mode: CurrentColors.base.mode, colors: defaultTheme?.colors ?? ThemeColors(), wallpaper: defaultTheme?.wallpaper ?? ThemeWallpaper.from(PresetWallpaper.school.toType(CurrentColors.base), nil, nil))
}
+ // Spec: spec/services/theme.md#currentColors
static func currentColors(_ themeOverridesForType: WallpaperType?, _ perChatTheme: ThemeModeOverride?, _ perUserTheme: ThemeModeOverrides?, _ appSettingsTheme: [ThemeOverrides]) -> ActiveTheme {
let themeName = currentThemeDefault.get()
let nonSystemThemeName = nonSystemThemeName()
@@ -96,6 +101,7 @@ class ThemeManager {
)
}
+ // Spec: spec/services/theme.md#currentThemeOverridesForExport
static func currentThemeOverridesForExport(_ themeOverridesForType: WallpaperType?, _ perChatTheme: ThemeModeOverride?, _ perUserTheme: ThemeModeOverrides?) -> ThemeOverrides {
let current = currentColors(themeOverridesForType, perChatTheme, perUserTheme, themeOverridesDefault.get())
let wType = current.wallpaper.type
@@ -114,6 +120,7 @@ class ThemeManager {
)
}
+ // Spec: spec/services/theme.md#applyTheme
static func applyTheme(_ theme: String) {
currentThemeDefault.set(theme)
CurrentColors = currentColors(nil, nil, ChatModel.shared.currentUser?.uiThemes, themeOverridesDefault.get())
@@ -125,6 +132,7 @@ class ThemeManager {
// applyNavigationBarColors(CurrentColors.toAppTheme())
}
+ // Spec: spec/services/theme.md#adjustWindowStyle
static func adjustWindowStyle() {
let style = switch currentThemeDefault.get() {
case DefaultTheme.LIGHT.themeName: UIUserInterfaceStyle.light
@@ -161,6 +169,7 @@ class ThemeManager {
AppTheme.shared.updateFromCurrentColors()
}
+ // Spec: spec/services/theme.md#saveAndApplyThemeColor
static func saveAndApplyThemeColor(_ baseTheme: DefaultTheme, _ name: ThemeColor, _ color: Color? = nil, _ pref: CodableDefault<[ThemeOverrides]>? = nil) {
let nonSystemThemeName = baseTheme.themeName
let pref = pref ?? themeOverridesDefault
@@ -178,6 +187,7 @@ class ThemeManager {
pref.wrappedValue = pref.wrappedValue.withUpdatedColor(name, color?.toReadableHex())
}
+ // Spec: spec/services/theme.md#saveAndApplyWallpaper
static func saveAndApplyWallpaper(_ baseTheme: DefaultTheme, _ type: WallpaperType?, _ pref: CodableDefault<[ThemeOverrides]>?) {
let nonSystemThemeName = baseTheme.themeName
let pref = pref ?? themeOverridesDefault
@@ -253,6 +263,7 @@ class ThemeManager {
pref.wrappedValue = prevValue
}
+ // Spec: spec/services/theme.md#saveAndApplyThemeOverrides
static func saveAndApplyThemeOverrides(_ theme: ThemeOverrides, _ pref: CodableDefault<[ThemeOverrides]>? = nil) {
let wallpaper = theme.wallpaper?.importFromString()
let nonSystemThemeName = theme.base.themeName
@@ -273,6 +284,7 @@ class ThemeManager {
applyTheme(nonSystemThemeName)
}
+ // Spec: spec/services/theme.md#resetAllThemeColors
static func resetAllThemeColors(_ pref: CodableDefault<[ThemeOverrides]>? = nil) {
let nonSystemThemeName = nonSystemThemeName()
let pref: CodableDefault<[ThemeOverrides]> = pref ?? themeOverridesDefault
@@ -295,6 +307,7 @@ class ThemeManager {
pref.wrappedValue = prevValue
}
+ // Spec: spec/services/theme.md#removeTheme
static func removeTheme(_ themeId: String?) {
var themes = themeOverridesDefault.get().map { $0 }
themes.removeAll(where: { $0.themeId == themeId })
diff --git a/apps/ios/Shared/Views/Call/ActiveCallView.swift b/apps/ios/Shared/Views/Call/ActiveCallView.swift
index ab7a47b944..754bcb2715 100644
--- a/apps/ios/Shared/Views/Call/ActiveCallView.swift
+++ b/apps/ios/Shared/Views/Call/ActiveCallView.swift
@@ -5,12 +5,14 @@
// Created by Evgeny on 05/05/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/services/calls.md
import SwiftUI
import WebKit
import SimpleXChat
import AVFoundation
+// Spec: spec/services/calls.md#ActiveCallView
struct ActiveCallView: View {
@EnvironmentObject var m: ChatModel
@Environment(\.colorScheme) var colorScheme
@@ -282,6 +284,7 @@ struct ActiveCallView: View {
}
}
+// Spec: spec/services/calls.md#ActiveCallOverlay
struct ActiveCallOverlay: View {
@EnvironmentObject var chatModel: ChatModel
@ObservedObject var call: Call
@@ -350,6 +353,7 @@ struct ActiveCallOverlay: View {
}
}
+ // Spec: spec/services/calls.md#audioCallInfoView
private func audioCallInfoView(_ call: Call) -> some View {
VStack {
Text(call.contact.chatViewName)
@@ -399,6 +403,7 @@ struct ActiveCallOverlay: View {
}
}
+ // Spec: spec/services/calls.md#endCallButton
private func endCallButton() -> some View {
let cc = CallController.shared
return callButton("phone.down.fill", .red, padding: 10) {
diff --git a/apps/ios/Shared/Views/Call/CallController.swift b/apps/ios/Shared/Views/Call/CallController.swift
index 1f28180e87..9df0c2f0b7 100644
--- a/apps/ios/Shared/Views/Call/CallController.swift
+++ b/apps/ios/Shared/Views/Call/CallController.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 21/05/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/services/calls.md
import Foundation
import CallKit
@@ -14,6 +15,7 @@ import AVFoundation
import SimpleXChat
import WebRTC
+// Spec: spec/services/calls.md#CallController
class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, ObservableObject {
static let shared = CallController()
static let isInChina = SKStorefront().countryCode == "CHN"
@@ -49,6 +51,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
logger.debug("CallController.providerDidReset")
}
+ // Spec: spec/services/calls.md#CXStartCallAction
func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
logger.debug("CallController.provider CXStartCallAction")
if callManager.startOutgoingCall(callUUID: action.callUUID.uuidString.lowercased()) {
@@ -59,6 +62,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
}
}
+ // Spec: spec/services/calls.md#CXAnswerCallAction
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
logger.debug("CallController.provider CXAnswerCallAction")
Task {
@@ -88,6 +92,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
}
}
+ // Spec: spec/services/calls.md#CXEndCallAction
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
logger.debug("CallController.provider CXEndCallAction")
// Should be nil here if connection was in connected state
@@ -103,6 +108,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
}
}
+ // Spec: spec/services/calls.md#CXSetMutedCallAction
func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
if callManager.enableMedia(source: .mic, enable: !action.isMuted, callUUID: action.callUUID.uuidString.lowercased()) {
action.fulfill()
@@ -192,6 +198,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
logger.debug("CallController: didUpdate push credentials for type \(type.rawValue)")
}
+ // Spec: spec/services/calls.md#pushRegistryDidReceive
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
logger.debug("CallController: did receive push with type \(type.rawValue)")
if type != .voIP {
@@ -276,6 +283,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
reportExpiredCall(update: update, completion)
}
+ // Spec: spec/services/calls.md#reportNewIncomingCall
func reportNewIncomingCall(invitation: RcvCallInvitation, completion: @escaping (Error?) -> Void) {
logger.debug("CallController.reportNewIncomingCall, UUID=\(String(describing: invitation.callUUID))")
if CallController.useCallKit(), let callUUID = invitation.callUUID, let uuid = UUID(uuidString: callUUID) {
@@ -316,6 +324,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
}
}
+ // Spec: spec/services/calls.md#reportOutgoingCall
func reportOutgoingCall(call: Call, connectedAt dateConnected: Date?) {
logger.debug("CallController: reporting outgoing call connected")
if CallController.useCallKit(), let callUUID = call.callUUID, let uuid = UUID(uuidString: callUUID) {
@@ -422,6 +431,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
provider.configuration = conf
}
+ // Spec: spec/services/calls.md#hasActiveCalls
func hasActiveCalls() -> Bool {
controller.callObserver.calls.count > 0
}
diff --git a/apps/ios/Shared/Views/Call/WebRTCClient.swift b/apps/ios/Shared/Views/Call/WebRTCClient.swift
index db7910836e..2ce04e4b80 100644
--- a/apps/ios/Shared/Views/Call/WebRTCClient.swift
+++ b/apps/ios/Shared/Views/Call/WebRTCClient.swift
@@ -2,12 +2,14 @@
// Created by Avently on 09.02.2023.
// Copyright (c) 2023 SimpleX Chat. All rights reserved.
//
+// Spec: spec/services/calls.md
import WebRTC
import LZString
import SwiftUI
import SimpleXChat
+// Spec: spec/services/calls.md#WebRTCClient
final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDelegate, RTCFrameDecryptorDelegate {
private static let factory: RTCPeerConnectionFactory = {
RTCInitializeSSL()
@@ -87,6 +89,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
WebRTC.RTCIceServer(urlStrings: ["turns:turn.simplex.im:443?transport=tcp"], username: "private2", credential: "Hxuq2QxUjnhj96Zq2r4HjqHRj"),
]
+ // Spec: spec/services/calls.md#initializeCall
func initializeCall(_ iceServers: [WebRTC.RTCIceServer]?, _ mediaType: CallMediaType, _ aesKey: String?, _ relay: Bool?) -> Call {
let connection = createPeerConnection(iceServers ?? getWebRTCIceServers() ?? defaultIceServers, relay)
connection.delegate = self
@@ -132,6 +135,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
)
}
+ // Spec: spec/services/calls.md#createPeerConnection
func createPeerConnection(_ iceServers: [WebRTC.RTCIceServer], _ relay: Bool?) -> RTCPeerConnection {
let constraints = RTCMediaConstraints(mandatoryConstraints: nil,
optionalConstraints: ["DtlsSrtpKeyAgreement": kRTCMediaConstraintsValueTrue])
@@ -157,6 +161,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
return config
}
+ // Spec: spec/services/calls.md#addIceCandidates
func addIceCandidates(_ connection: RTCPeerConnection, _ remoteIceCandidates: [RTCIceCandidate]) {
remoteIceCandidates.forEach { candidate in
connection.add(candidate.toWebRTCCandidate()) { error in
@@ -167,6 +172,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
}
}
+ // Spec: spec/services/calls.md#sendCallCommand
func sendCallCommand(command: WCallCommand) async {
var resp: WCallResponse? = nil
let pc = activeCall?.connection
@@ -295,6 +301,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
}
}
+ // Spec: spec/services/calls.md#sendIceCandidates
func sendIceCandidates(_ candidates: [RTCIceCandidate]) async {
await self.sendCallResponse(.init(
corrId: nil,
@@ -353,6 +360,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
}
}
+ // Spec: spec/services/calls.md#enableMedia
@MainActor
func enableMedia(_ source: CallMediaSource, _ enable: Bool) {
logger.debug("WebRTCClient: enabling media \(source.rawValue) \(enable)")
@@ -411,6 +419,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
localRendererAspectRatio.wrappedValue = size.width / size.height
}
+ // Spec: spec/services/calls.md#setupLocalTracks
func setupLocalTracks(_ incomingCall: Bool, _ call: Call) {
let pc = call.connection
let transceivers = call.connection.transceivers
@@ -490,6 +499,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
}
// Should be called after local description set
+ // Spec: spec/services/calls.md#setupEncryptionForLocalTracks
func setupEncryptionForLocalTracks(_ call: Call) {
if let encryptor = call.frameEncryptor {
call.connection.senders.forEach { $0.setRtcFrameEncryptor(encryptor) }
@@ -567,6 +577,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
}
}
+ // Spec: spec/services/calls.md#startCaptureLocalVideo
func startCaptureLocalVideo(_ device: AVCaptureDevice.Position?, _ capturer: RTCVideoCapturer?) {
#if targetEnvironment(simulator)
guard
@@ -630,6 +641,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
return (localCamera, localVideoTrack)
}
+ // Spec: spec/services/calls.md#endCall
func endCall() {
if #available(iOS 16.0, *) {
_endCall()
diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift
index ad82af05e2..c17d8e23a8 100644
--- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift
@@ -5,6 +5,7 @@
// Created by Evgeny Poberezkin on 05/02/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
@preconcurrency import SimpleXChat
@@ -88,6 +89,7 @@ enum SendReceipts: Identifiable, Hashable {
}
}
+// Spec: spec/client/chat-view.md#ChatInfoView
struct ChatInfoView: View {
@EnvironmentObject var chatModel: ChatModel
@EnvironmentObject var theme: AppTheme
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/AnimatedImageView.swift b/apps/ios/Shared/Views/Chat/ChatItem/AnimatedImageView.swift
index 30f5e7a589..93ffb9f042 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/AnimatedImageView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/AnimatedImageView.swift
@@ -2,10 +2,12 @@
// Created by Avently on 19.12.2022.
// Copyright (c) 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import UIKit
import SwiftUI
+// Spec: spec/client/chat-view.md#AnimatedImageView
class AnimatedImageView: UIView {
var image: UIImage? = nil
var imageView: UIImageView? = nil
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CICallItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CICallItemView.swift
index 0283e9c07e..e5f3c05eed 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/CICallItemView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/CICallItemView.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 20/05/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import SimpleXChat
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIChatFeatureView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIChatFeatureView.swift
index b2b4441646..5521470d07 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/CIChatFeatureView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/CIChatFeatureView.swift
@@ -5,10 +5,12 @@
// Created by Evgeny on 21/11/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import SimpleXChat
+// Spec: spec/client/chat-view.md#CIChatFeatureView
struct CIChatFeatureView: View {
@EnvironmentObject var m: ChatModel
@Environment(\.revealed) var revealed: Bool
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIEventView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIEventView.swift
index 1375b87a5a..49a086d45a 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/CIEventView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/CIEventView.swift
@@ -5,10 +5,12 @@
// Created by JRoberts on 20.07.2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import SimpleXChat
+// Spec: spec/client/chat-view.md#CIEventView
struct CIEventView: View {
var eventText: Text
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIFeaturePreferenceView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIFeaturePreferenceView.swift
index 67f7b69e2c..dcd6ea579c 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/CIFeaturePreferenceView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/CIFeaturePreferenceView.swift
@@ -5,10 +5,12 @@
// Created by Evgeny on 21/12/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import SimpleXChat
+// Spec: spec/client/chat-view.md#CIFeaturePreferenceView
struct CIFeaturePreferenceView: View {
@ObservedObject var chat: Chat
@EnvironmentObject var theme: AppTheme
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift
index 1b9376b5db..639de1dbc9 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift
@@ -5,10 +5,12 @@
// Created by JRoberts on 28/04/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import SimpleXChat
+// Spec: spec/client/chat-view.md#CIFileView
struct CIFileView: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift
index 3fcf578875..ddb58fdfd1 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift
@@ -5,10 +5,12 @@
// Created by JRoberts on 15.07.2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import SimpleXChat
+// Spec: spec/client/chat-view.md#CIGroupInvitationView
struct CIGroupInvitationView: View {
@EnvironmentObject var chatModel: ChatModel
@EnvironmentObject var theme: AppTheme
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift
index d1f49f635a..8b5172eccf 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift
@@ -5,10 +5,12 @@
// Created by JRoberts on 12/04/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import SimpleXChat
+// Spec: spec/client/chat-view.md#CIImageView
struct CIImageView: View {
@EnvironmentObject var m: ChatModel
let chatItem: ChatItem
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift
index 5e9fa691de..80cccbf907 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift
@@ -5,10 +5,12 @@
// Created by JRoberts on 29.12.2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import SimpleXChat
+// Spec: spec/client/chat-view.md#CIInvalidJSONView
struct CIInvalidJSONView: View {
@EnvironmentObject var theme: AppTheme
var json: Data?
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift
index f07e90b953..a09518ffdb 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift
@@ -5,10 +5,12 @@
// Created by Ian Davies on 07/04/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import SimpleXChat
+// Spec: spec/client/chat-view.md#CILinkView
struct CILinkView: View {
@EnvironmentObject var theme: AppTheme
let linkPreview: LinkPreview
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift
index 2898a318a9..4719c3dcdc 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift
@@ -5,10 +5,12 @@
// Created by spaced4ndy on 19.09.2023.
// Copyright © 2023 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import SimpleXChat
+// Spec: spec/client/chat-view.md#CIMemberCreatedContactView
struct CIMemberCreatedContactView: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift
index fc73778239..e3bc654ac9 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift
@@ -5,10 +5,12 @@
// Created by Evgeny Poberezkin on 11/02/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import SimpleXChat
+// Spec: spec/client/chat-view.md#CIMetaView
struct CIMetaView: View {
@ObservedObject var chat: Chat
@EnvironmentObject var theme: AppTheme
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift
index 3201332c1e..ec23dc15a4 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift
@@ -5,12 +5,14 @@
// Created by Evgeny on 15/04/2023.
// Copyright © 2023 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import SimpleXChat
let decryptErrorReason: LocalizedStringKey = "It can happen when you or your connection used the old database backup."
+// Spec: spec/client/chat-view.md#CIRcvDecryptionError
struct CIRcvDecryptionError: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift
index eacbe9360a..80bea997d3 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift
@@ -5,12 +5,14 @@
// Created by Avently on 30/03/2023.
// Copyright © 2023 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import AVKit
import SimpleXChat
import Combine
+// Spec: spec/client/chat-view.md#CIVideoView
struct CIVideoView: View {
@EnvironmentObject var m: ChatModel
private let chatItem: ChatItem
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift
index 47aee2a586..820074542f 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift
@@ -5,10 +5,12 @@
// Created by JRoberts on 22.11.2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import SimpleXChat
+// Spec: spec/client/chat-view.md#CIVoiceView
struct CIVoiceView: View {
@ObservedObject var chat: Chat
@EnvironmentObject var theme: AppTheme
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/DeletedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/DeletedItemView.swift
index ed2340b6c4..fb5d36ab12 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/DeletedItemView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/DeletedItemView.swift
@@ -5,10 +5,12 @@
// Created by JRoberts on 04/02/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import SimpleXChat
+// Spec: spec/client/chat-view.md#DeletedItemView
struct DeletedItemView: View {
@EnvironmentObject var theme: AppTheme
@ObservedObject var chat: Chat
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/EmojiItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/EmojiItemView.swift
index 250d9d5636..04f36c97a4 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/EmojiItemView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/EmojiItemView.swift
@@ -5,10 +5,12 @@
// Created by Evgeny Poberezkin on 04/02/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import SimpleXChat
+// Spec: spec/client/chat-view.md#EmojiItemView
struct EmojiItemView: View {
@ObservedObject var chat: Chat
@EnvironmentObject var theme: AppTheme
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift
index 0b6f249b9c..123f7289bb 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift
@@ -5,12 +5,14 @@
// Created by JRoberts on 22.11.2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import SwiftUI
import SimpleXChat
+// Spec: spec/client/chat-view.md#FramedCIVoiceView
struct FramedCIVoiceView: View {
@EnvironmentObject var theme: AppTheme
@ObservedObject var chat: Chat
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift
index c9c9952688..ec8bc852c0 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift
@@ -5,10 +5,12 @@
// Created by Evgeny Poberezkin on 04/02/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import SimpleXChat
+// Spec: spec/client/chat-view.md#FramedItemView
struct FramedItemView: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift
index f243a83142..e14683684d 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift
@@ -5,12 +5,14 @@
// Created by Evgeny on 08/10/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import SimpleXChat
import SwiftyGif
import AVKit
+// Spec: spec/client/chat-view.md#FullScreenMediaView
struct FullScreenMediaView: View {
@EnvironmentObject var m: ChatModel
@State var chatItem: ChatItem
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift
index 47a30f6cf3..fdf3743aac 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift
@@ -5,10 +5,12 @@
// Created by Evgeny on 28/05/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import SimpleXChat
+// Spec: spec/client/chat-view.md#IntegrityErrorItemView
struct IntegrityErrorItemView: View {
@ObservedObject var chat: Chat
@EnvironmentObject var theme: AppTheme
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift
index c6a5d0353c..953f4e8c82 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift
@@ -5,10 +5,12 @@
// Created by JRoberts on 30.11.2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import SimpleXChat
+// Spec: spec/client/chat-view.md#MarkedDeletedItemView
struct MarkedDeletedItemView: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift
index 2a1b526893..77bd41c5b8 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 13/03/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import SimpleXChat
@@ -23,6 +24,7 @@ private func typing(_ theme: AppTheme, _ descr: UIFontDescriptor, _ ws: [UIFont.
return res
}
+// Spec: spec/client/chat-view.md#MsgContentView
struct MsgContentView: View {
@ObservedObject var chat: Chat
@Environment(\.showTimestamp) var showTimestamp: Bool
@@ -320,6 +322,7 @@ func messageText(
var bold: UIFont?
var italic: UIFont?
var snippet: UIFont?
+ var small: UIFont?
var mention: UIFont?
var secretIdx: Int = 0
for ft in fts {
@@ -351,6 +354,10 @@ func messageText(
attrs[.backgroundColor] = secretColor
}
hasSecrets = true
+ case .small:
+ small = small ?? UIFont.preferredFont(forTextStyle: .footnote)
+ attrs[.font] = small
+ attrs[.foregroundColor] = UIColor.secondaryLabel
case let .colored(color):
if let c = color.uiColor {
attrs[.foregroundColor] = UIColor(c)
diff --git a/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift b/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift
index 87c6ba92f8..3858d15252 100644
--- a/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift
@@ -9,6 +9,7 @@
import SwiftUI
import SimpleXChat
+// Spec: spec/client/chat-view.md#ChatItemInfoView
struct ChatItemInfoView: View {
@EnvironmentObject var chatModel: ChatModel
@Environment(\.dismiss) var dismiss
diff --git a/apps/ios/Shared/Views/Chat/ChatItemView.swift b/apps/ios/Shared/Views/Chat/ChatItemView.swift
index 5f48c18881..f72bf083f6 100644
--- a/apps/ios/Shared/Views/Chat/ChatItemView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItemView.swift
@@ -38,6 +38,7 @@ extension EnvironmentValues {
}
}
+// Spec: spec/client/chat-view.md#ChatItemView
struct ChatItemView: View {
@ObservedObject var chat: Chat
@ObservedObject var im: ItemsModel
diff --git a/apps/ios/Shared/Views/Chat/ChatItemsLoader.swift b/apps/ios/Shared/Views/Chat/ChatItemsLoader.swift
index 93ecf870eb..9987fb4697 100644
--- a/apps/ios/Shared/Views/Chat/ChatItemsLoader.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItemsLoader.swift
@@ -15,6 +15,7 @@ func apiLoadMessages(
_ chatId: ChatId,
_ im: ItemsModel,
_ pagination: ChatPagination,
+ _ contentTag: MsgContentTag? = nil,
_ search: String = "",
_ openAroundItemId: ChatItem.ID? = nil,
_ visibleItemIndexesNonReversed: @MainActor () -> ClosedRange = { 0 ... 0 }
@@ -22,7 +23,7 @@ func apiLoadMessages(
let chat: Chat
let navInfo: NavigationInfo
do {
- (chat, navInfo) = try await apiGetChat(chatId: chatId, scope: im.groupScopeInfo?.toChatScope(), contentTag: im.contentTag, pagination: pagination, search: search)
+ (chat, navInfo) = try await apiGetChat(chatId: chatId, scope: im.groupScopeInfo?.toChatScope(), contentTag: contentTag ?? im.contentTag, pagination: pagination, search: search)
} catch let error {
logger.error("apiLoadMessages error: \(responseError(error))")
return
diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift
index 709758655f..057bf7f75f 100644
--- a/apps/ios/Shared/Views/Chat/ChatView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatView.swift
@@ -5,6 +5,7 @@
// Created by Evgeny Poberezkin on 27/01/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import SimpleXChat
@@ -13,6 +14,7 @@ import Combine
private let memberImageSize: CGFloat = 34
+// Spec: spec/client/chat-view.md#ChatView
struct ChatView: View {
@EnvironmentObject var chatModel: ChatModel
@StateObject private var connectProgressManager = ConnectProgressManager.shared
@@ -44,6 +46,8 @@ struct ChatView: View {
@State private var showSearch = false
@State private var searchText: String = ""
@FocusState private var searchFocussed
+ @State private var contentFilter: ContentFilter? = nil
+ @State private var availableContent: [ContentFilter] = [.images, .files, .links]
// opening GroupMemberInfoView on member icon
@State private var selectedMember: GMember? = nil
// opening GroupLinkView on link button (incognito)
@@ -68,6 +72,7 @@ struct ChatView: View {
let userSupportScopeInfo: GroupChatScopeInfo = .memberSupport(groupMember_: nil)
+ // Spec: spec/client/chat-view.md#body
var body: some View {
if #available(iOS 16.0, *) {
viewBody
@@ -528,16 +533,19 @@ struct ChatView: View {
case let .direct(contact):
HStack {
let callsPrefEnabled = contact.mergedPreferences.calls.enabled.forUser
- if callsPrefEnabled {
- if chatModel.activeCall == nil {
- callButton(contact, .audio, imageName: "phone")
- .disabled(!contact.ready || !contact.active)
- } else if let call = chatModel.activeCall, call.contact.id == cInfo.id {
- endCallButton(call)
- }
+ if let call = chatModel.activeCall, call.contact.id == cInfo.id {
+ endCallButton(call)
+ } else {
+ contentFilterMenu(withLabel: false)
}
Menu {
if callsPrefEnabled && chatModel.activeCall == nil {
+ Button {
+ CallController.shared.startCall(contact, .audio)
+ } label: {
+ Label("Audio call", systemImage: "phone")
+ }
+ .disabled(!contact.ready || !contact.active)
Button {
CallController.shared.startCall(contact, .video)
} label: {
@@ -545,6 +553,9 @@ struct ChatView: View {
}
.disabled(!contact.ready || !contact.active)
}
+ if let call = chatModel.activeCall, call.contact.id == cInfo.id {
+ contentFilterMenu(withLabel: true)
+ }
searchButton()
ToggleNtfsButton(chat: chat)
.disabled(!contact.ready || !contact.active)
@@ -554,23 +565,24 @@ struct ChatView: View {
}
case let .group(groupInfo, _):
HStack {
- if groupInfo.canAddMembers {
- if (chat.chatInfo.incognito) {
- groupLinkButton()
- .appSheet(isPresented: $showGroupLinkSheet) {
- GroupLinkView(
- groupId: groupInfo.groupId,
- groupLink: $groupLink,
- groupLinkMemberRole: $groupLinkMemberRole,
- showTitle: true,
- creatingGroup: false
- )
- }
- } else {
- addMembersButton()
- }
- }
+ contentFilterMenu(withLabel: false)
Menu {
+ if groupInfo.canAddMembers {
+ if (chat.chatInfo.incognito) {
+ groupLinkButton()
+ .appSheet(isPresented: $showGroupLinkSheet) {
+ GroupLinkView(
+ groupId: groupInfo.groupId,
+ groupLink: $groupLink,
+ groupLinkMemberRole: $groupLinkMemberRole,
+ showTitle: true,
+ creatingGroup: false
+ )
+ }
+ } else {
+ addMembersButton()
+ }
+ }
searchButton()
ToggleNtfsButton(chat: chat)
} label: {
@@ -578,7 +590,10 @@ struct ChatView: View {
}
}
case .local:
- searchButton()
+ HStack {
+ contentFilterMenu(withLabel: false)
+ searchButton()
+ }
default:
EmptyView()
}
@@ -656,6 +671,7 @@ struct ChatView: View {
.frame(width: 220)
}
+ // Spec: spec/client/chat-view.md#initChatView
private func initChatView() {
let cInfo = chat.chatInfo
// This check prevents the call to apiContactInfo after the app is suspended, and the database is closed.
@@ -685,6 +701,7 @@ struct ChatView: View {
}
}
}
+ updateAvailableContent()
}
if chatModel.draftChatId == cInfo.id && !composeState.forwarding,
let draft = chatModel.draft {
@@ -698,6 +715,23 @@ struct ChatView: View {
floatingButtonModel.updateOnListChange(scrollView.listState)
}
+ private func updateAvailableContent() {
+ Task {
+ let content: [ContentFilter]
+ do {
+ let contentTags = Set(try await apiGetChatContentTypes(chatId: chat.chatInfo.id)).union(ContentFilter.alwaysShow)
+ content = ContentFilter.allCases.filter { contentTags.contains($0.contentTag) }
+ } catch let error {
+ logger.error("apiGetChatContentTypes error: \(responseError(error))")
+ content = ContentFilter.allCases
+ }
+ await MainActor.run {
+ availableContent = content
+ }
+ }
+ }
+
+ // Spec: spec/client/chat-view.md#scrollToItem
private func scrollToItem(_ itemId: ChatItem.ID) {
Task {
do {
@@ -731,11 +765,16 @@ struct ChatView: View {
}
}
+ // Spec: spec/client/chat-view.md#searchToolbar
private func searchToolbar() -> some View {
- HStack(spacing: 12) {
+ let placeholder: LocalizedStringKey = contentFilter?.searchPlaceholder ?? "Search"
+ return HStack(spacing: 12) {
HStack(spacing: 4) {
Image(systemName: "magnifyingglass")
- TextField("Search", text: $searchText)
+ if let contentFilter {
+ Image(systemName: contentFilter.icon)
+ }
+ TextField(placeholder, text: $searchText)
.focused($searchFocussed)
.foregroundColor(theme.colors.onBackground)
.frame(maxWidth: .infinity)
@@ -764,6 +803,7 @@ struct ChatView: View {
ci.content.msgContent?.isVoice == true && ci.content.text.count == 0 && ci.quotedItem == nil && ci.meta.itemForwarded == nil
}
+ // Spec: spec/client/chat-view.md#filtered
private func filtered(_ reversedChatItems: Array) -> Array {
reversedChatItems
.enumerated()
@@ -777,6 +817,7 @@ struct ChatView: View {
.map { $0.element }
}
+ // Spec: spec/client/chat-view.md#chatItemsList
private func chatItemsList() -> some View {
let cInfo = chat.chatInfo
return GeometryReader { g in
@@ -1050,9 +1091,10 @@ struct ChatView: View {
}
}
+ // Spec: spec/client/chat-view.md#searchTextChanged
private func searchTextChanged(_ s: String) {
Task {
- await loadChat(chat: chat, im: im, search: s)
+ await loadChat(chat: chat, im: im, contentTag: contentFilter?.contentTag, search: s)
mergedItems.boxedValue = MergedItems.create(im, revealedItems)
await MainActor.run {
scrollView.updateItems(mergedItems.boxedValue.items)
@@ -1227,6 +1269,7 @@ struct ChatView: View {
}
}
+ // Spec: spec/client/chat-view.md#callButton
private func callButton(_ contact: Contact, _ media: CallMediaType, imageName: String) -> some View {
Button {
CallController.shared.startCall(contact, media)
@@ -1255,16 +1298,52 @@ struct ChatView: View {
}
}
+ private func contentFilterMenu(withLabel: Bool) -> some View {
+ Menu {
+ ForEach(availableContent, id: \.self) { type in
+ Button {
+ setContentFilter(type)
+ } label: {
+ Label(type.label, systemImage: contentFilter == type ? type.iconFilled : type.icon)
+ }
+ }
+ if contentFilter != nil {
+ Button {
+ closeSearch()
+ } label: {
+ Label("All messages", systemImage: "bubble.left.and.text.bubble.right")
+ }
+ }
+ } label: {
+ let icon = contentFilter == nil ? "photo.on.rectangle" : "photo.on.rectangle.fill"
+ if withLabel {
+ Label("Filter", systemImage: icon)
+ } else {
+ Image(systemName: icon)
+ }
+ }
+ }
+
private func focusSearch() {
showSearch = true
searchFocussed = true
searchText = ""
}
+ private func setContentFilter(_ type: ContentFilter) {
+ if (contentFilter == type) { return }
+ contentFilter = type
+ showSearch = true
+ searchText = ""
+ searchTextChanged("")
+ }
+
private func closeSearch() {
showSearch = false
searchText = ""
searchFocussed = false
+ contentFilter = nil
+ updateAvailableContent()
}
private func closeKeyboardAndRun(_ action: @escaping () -> Void) {
@@ -1285,7 +1364,7 @@ struct ChatView: View {
Task { await chatModel.loadGroupMembers(gInfo) { showAddMembersSheet = true } }
}
} label: {
- Image(systemName: "person.crop.circle.badge.plus")
+ Label("Invite member", systemImage: "person.crop.circle.badge.plus")
}
}
@@ -1305,7 +1384,7 @@ struct ChatView: View {
}
}
} label: {
- Image(systemName: "link.badge.plus")
+ Label("Group link", systemImage: "link.badge.plus")
}
}
@@ -1328,6 +1407,7 @@ struct ChatView: View {
))
}
+ // Spec: spec/client/chat-view.md#deletedSelectedMessages
private func deletedSelectedMessages() async {
await MainActor.run {
withAnimation {
@@ -1336,6 +1416,7 @@ struct ChatView: View {
}
}
+ // Spec: spec/client/chat-view.md#forwardSelectedMessages
private func forwardSelectedMessages() {
Task {
do {
@@ -1446,6 +1527,7 @@ struct ChatView: View {
}
}
+ // Spec: spec/client/chat-view.md#loadChatItems
private func loadChatItems(_ chat: Chat, _ pagination: ChatPagination) async -> Bool {
if loadingMoreItems { return false }
await MainActor.run {
@@ -1473,6 +1555,7 @@ struct ChatView: View {
chat.chatInfo.id,
im,
pagination,
+ contentFilter?.contentTag,
searchText,
nil,
{ visibleItemIndexesNonReversed(im, scrollView.listState, mergedItems.boxedValue) }
@@ -1485,6 +1568,7 @@ struct ChatView: View {
VoiceItemState.chatView = [:]
}
+ // Spec: spec/client/chat-view.md#onChatItemsUpdated
func onChatItemsUpdated() {
if !mergedItems.boxedValue.isActualState() {
//logger.debug("Items are not actual, waiting for the next update: \(String(describing: mergedItems.boxedValue.splits)) \(im.chatState.splits), \(mergedItems.boxedValue.indexInParentItems.count) vs \(im.reversedChatItems.count)")
@@ -1512,6 +1596,7 @@ struct ChatView: View {
)
}
+ // Spec: spec/client/chat-view.md#ChatItemWithMenu
private struct ChatItemWithMenu: View {
@ObservedObject var im: ItemsModel
@EnvironmentObject var m: ChatModel
@@ -2623,6 +2708,7 @@ struct ChatView: View {
}
}
+// Spec: spec/client/chat-view.md#FloatingButtonModel
class FloatingButtonModel: ObservableObject {
@ObservedObject var im: ItemsModel
@@ -2705,6 +2791,7 @@ private func broadcastDeleteButtonText(_ chat: Chat) -> LocalizedStringKey {
chat.chatInfo.featureEnabled(.fullDelete) ? "Delete for everyone" : "Mark deleted for everyone"
}
+// Spec: spec/client/chat-view.md#deleteMessages
private func deleteMessages(_ chat: Chat, _ deletingItems: [Int64], _ mode: CIDeleteMode = .cidmInternal, moderate: Bool, _ onSuccess: @escaping () async -> Void = {}) {
let itemIds = deletingItems
if itemIds.count > 0 {
@@ -2808,6 +2895,7 @@ private func buildTheme() -> AppTheme {
}
}
+// Spec: spec/client/chat-view.md#ReactionContextMenu
struct ReactionContextMenu: View {
@EnvironmentObject var m: ChatModel
let groupInfo: GroupInfo
@@ -2957,6 +3045,67 @@ func updateChatSettings(_ chat: Chat, chatSettings: ChatSettings) {
}
}
+// Spec: spec/client/chat-view.md#ContentFilter
+enum ContentFilter: CaseIterable {
+ case images
+ case videos
+ case voice
+ case files
+ case links
+
+ static let alwaysShow: Set = [.image, .link]
+
+ var contentTag: MsgContentTag {
+ switch self {
+ case .images: .image
+ case .videos: .video
+ case .voice: .voice
+ case .files: .file
+ case .links: .link
+ }
+ }
+
+ var label: LocalizedStringKey {
+ switch self {
+ case .images: "Images"
+ case .videos: "Videos"
+ case .voice: "Voice messages"
+ case .files: "Files"
+ case .links: "Links"
+ }
+ }
+
+ var searchPlaceholder: LocalizedStringKey {
+ switch self {
+ case .images: "Search images"
+ case .videos: "Search videos"
+ case .voice: "Search voice messages"
+ case .files: "Search files"
+ case .links: "Search links"
+ }
+ }
+
+ var icon: String {
+ switch self {
+ case .images: "photo"
+ case .videos: "video"
+ case .voice: "mic"
+ case .files: "doc"
+ case .links: "link"
+ }
+ }
+
+ var iconFilled: String {
+ switch self {
+ case .images: "photo.fill"
+ case .videos: "video.fill"
+ case .voice: "mic.fill"
+ case .files: "doc.fill"
+ case .links: "link.circle.fill"
+ }
+ }
+}
+
struct ChatView_Previews: PreviewProvider {
static var previews: some View {
let chatModel = ChatModel()
diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift
index 3745d0f0b8..2c462df9e4 100644
--- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift
+++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift
@@ -1,3 +1,4 @@
+// Spec: spec/client/compose.md
import SwiftUI
import SimpleXChat
@@ -6,6 +7,7 @@ import PhotosUI
let MAX_NUMBER_OF_MENTIONS = 3
+// Spec: spec/client/compose.md#ComposePreview
enum ComposePreview {
case noPreview
case linkPreview(linkPreview: LinkPreview?)
@@ -14,6 +16,7 @@ enum ComposePreview {
case filePreview(fileName: String, file: URL)
}
+// Spec: spec/client/compose.md#ComposeContextItem
enum ComposeContextItem: Equatable {
case noContextItem
case quotedItem(chatItem: ChatItem)
@@ -22,12 +25,14 @@ enum ComposeContextItem: Equatable {
case reportedItem(chatItem: ChatItem, reason: ReportReason)
}
+// Spec: spec/client/compose.md#VoiceMessageRecordingState
enum VoiceMessageRecordingState {
case noRecording
case recording
case finished
}
+// Spec: spec/client/compose.md#LiveMessage
struct LiveMessage {
var chatItem: ChatItem
var typedMsg: String
@@ -36,6 +41,7 @@ struct LiveMessage {
typealias MentionedMembers = [String: CIMention]
+// Spec: spec/client/compose.md#ComposeState
struct ComposeState {
var message: String
var parsedMessage: [FormattedText]
@@ -256,6 +262,7 @@ struct ComposeState {
}
}
+// Spec: spec/client/compose.md#chatItemPreview
func chatItemPreview(chatItem: ChatItem) -> ComposePreview {
switch chatItem.content.msgContent {
case .text:
@@ -276,6 +283,7 @@ func chatItemPreview(chatItem: ChatItem) -> ComposePreview {
}
}
+// Spec: spec/client/compose.md#UploadContent
enum UploadContent: Equatable {
case simpleImage(image: UIImage)
case animatedImage(image: UIImage)
@@ -317,6 +325,7 @@ enum UploadContent: Equatable {
}
}
+// Spec: spec/client/compose.md#ComposeView
struct ComposeView: View {
@EnvironmentObject var chatModel: ChatModel
@EnvironmentObject var theme: AppTheme
@@ -356,6 +365,7 @@ struct ComposeView: View {
@AppStorage(GROUP_DEFAULT_PRIVACY_SANITIZE_LINKS, store: groupDefaults) private var privacySanitizeLinks = false
@State private var updatingCompose = false
+ // Spec: spec/client/compose.md#body
var body: some View {
VStack(spacing: 0) {
Divider()
@@ -679,6 +689,7 @@ struct ComposeView: View {
.padding(.horizontal, 12)
}
+ // Spec: spec/client/compose.md#sendMessageView
private func sendMessageView(_ disableSendButton: Bool, placeholder: String? = nil, sendToConnect: (() -> Void)? = nil) -> some View {
ZStack(alignment: .leading) {
SendMessageView(
@@ -878,6 +889,7 @@ struct ComposeView: View {
}
}
+ // Spec: spec/client/compose.md#addMediaContent
private func addMediaContent(_ content: UploadContent) async {
if let img = await resizeImageToStrSize(content.uiImage, maxDataSize: 14000) {
var newMedia: [(String, UploadContent?)] = []
@@ -906,6 +918,7 @@ struct ComposeView: View {
getMaxFileSize(.xftp)
}
+ // Spec: spec/client/compose.md#sendLiveMessage
private func sendLiveMessage() async {
let typedMsg = composeState.message
let lm = composeState.liveMessage
@@ -923,6 +936,7 @@ struct ComposeView: View {
}
}
+ // Spec: spec/client/compose.md#updateLiveMessage
private func updateLiveMessage() async {
let typedMsg = composeState.message
if let liveMessage = composeState.liveMessage {
@@ -941,6 +955,7 @@ struct ComposeView: View {
}
}
+ // Spec: spec/client/compose.md#liveMessageToSend
private func liveMessageToSend(_ lm: LiveMessage, _ t: String) -> String? {
let s = t != lm.typedMsg ? truncateToWords(t) : t
return s != lm.sentMsg && (lm.sentMsg != nil || !s.isEmpty) ? s : nil
@@ -1087,6 +1102,7 @@ struct ComposeView: View {
}
}
+ // Spec: spec/client/compose.md#sendMessage
private func sendMessage(ttl: Int?) {
logger.debug("ChatView sendMessage")
Task {
@@ -1095,6 +1111,7 @@ struct ComposeView: View {
}
}
+ // Spec: spec/client/compose.md#sendMessageAsync
private func sendMessageAsync(_ text: String?, live: Bool, ttl: Int?) async -> ChatItem? {
var sent: ChatItem?
let msgText = text ?? composeState.message
@@ -1361,6 +1378,7 @@ struct ComposeView: View {
await MainActor.run { composeState.inProgress = true }
}
+ // Spec: spec/client/compose.md#startVoiceMessageRecording
private func startVoiceMessageRecording() async {
startingRecording = true
let fileName = generateNewFileName("voice", "m4a")
@@ -1401,6 +1419,7 @@ struct ComposeView: View {
}
}
+ // Spec: spec/client/compose.md#finishVoiceMessageRecording
private func finishVoiceMessageRecording() {
audioRecorder?.stop()
audioRecorder = nil
@@ -1411,6 +1430,7 @@ struct ComposeView: View {
}
}
+ // Spec: spec/client/compose.md#allowVoiceMessagesToContact
private func allowVoiceMessagesToContact() {
if case let .direct(contact) = chat.chatInfo {
allowFeatureToContact(contact, .voice)
@@ -1436,12 +1456,14 @@ struct ComposeView: View {
}
}
+ // Spec: spec/client/compose.md#cancelVoiceMessageRecording
private func cancelVoiceMessageRecording(_ fileName: String) {
stopPlayback.toggle()
audioRecorder?.stop()
removeFile(fileName)
}
+ // Spec: spec/client/compose.md#clearState
private func clearState(live: Bool = false) {
if live {
composeState.inProgress = false
@@ -1455,11 +1477,13 @@ struct ComposeView: View {
startingRecording = false
}
+ // Spec: spec/client/compose.md#saveCurrentDraft
private func saveCurrentDraft() {
chatModel.draft = composeState
chatModel.draftChatId = chat.id
}
+ // Spec: spec/client/compose.md#clearCurrentDraft
private func clearCurrentDraft() {
if chatModel.draftChatId == chat.id {
chatModel.draft = nil
@@ -1467,6 +1491,7 @@ struct ComposeView: View {
}
}
+ // Spec: spec/client/compose.md#showLinkPreview
private func showLinkPreview(_ parsedMsg: [FormattedText]?) {
prevLinkUrl = linkUrl
(linkUrl, hasSimplexLink) = getMessageLinks(parsedMsg)
@@ -1486,6 +1511,7 @@ struct ComposeView: View {
}
}
+ // Spec: spec/client/compose.md#getMessageLinks
private func getMessageLinks(_ parsedMsg: [FormattedText]?) -> (url: String?, hasSimplexLink: Bool) {
guard let parsedMsg else { return (nil, false) }
let simplexLink = parsedMsgHasSimplexLink(parsedMsg)
@@ -1512,6 +1538,7 @@ struct ComposeView: View {
composeState = composeState.copy(preview: .noPreview)
}
+ // Spec: spec/client/compose.md#loadLinkPreview
private func loadLinkPreview(_ urlStr: String) {
if pendingLinkUrl == urlStr, let url = URL(string: urlStr) {
composeState = composeState.copy(preview: .linkPreview(linkPreview: nil))
diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift
index 07cd61583b..713f462c27 100644
--- a/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift
+++ b/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift
@@ -11,6 +11,7 @@ import SimpleXChat
private let liveMsgInterval: UInt64 = 3000_000000
+// Spec: spec/client/compose.md#SendMessageView
struct SendMessageView: View {
var placeholder: String?
@Binding var composeState: ComposeState
diff --git a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift
index 3154f16f5b..6b18c0c5ef 100644
--- a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift
+++ b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift
@@ -5,6 +5,7 @@
// Created by JRoberts on 22.07.2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import SimpleXChat
diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift
index 96b5e2898a..4113b75d0a 100644
--- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift
+++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift
@@ -5,12 +5,14 @@
// Created by JRoberts on 14.07.2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import SimpleXChat
let SMALL_GROUPS_RCPS_MEM_LIMIT: Int = 20
+// Spec: spec/client/chat-view.md#GroupChatInfoView
struct GroupChatInfoView: View {
@EnvironmentObject var chatModel: ChatModel
@EnvironmentObject var theme: AppTheme
@@ -453,7 +455,9 @@ struct GroupChatInfoView: View {
}
private func memberConnStatus(_ member: GroupMember) -> LocalizedStringKey {
- if member.activeConn?.connDisabled ?? false {
+ if case .failed = member.activeConn?.connStatus {
+ return "failed"
+ } else if member.activeConn?.connDisabled ?? false {
return "disabled"
} else if member.activeConn?.connInactive ?? false {
return "inactive"
diff --git a/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift b/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift
index bc1ac4ab65..43bc26e8f8 100644
--- a/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift
+++ b/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift
@@ -5,6 +5,7 @@
// Created by JRoberts on 15.10.2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import SimpleXChat
diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift
index 207c2170a3..135efae74f 100644
--- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift
+++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift
@@ -5,6 +5,7 @@
// Created by JRoberts on 25.07.2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-view.md
import SwiftUI
import SimpleXChat
@@ -188,6 +189,12 @@ struct GroupMemberInfoView: View {
}
}
+ if let connFailedErr = member.activeConn?.connFailedErr {
+ Section {
+ infoRow("Connection failed", connFailedErr)
+ }
+ }
+
if groupInfo.membership.memberRole >= .moderator {
adminDestructiveSection(member)
} else {
diff --git a/apps/ios/Shared/Views/Chat/Group/MemberSupportView.swift b/apps/ios/Shared/Views/Chat/Group/MemberSupportView.swift
index 75a6840c4e..3dc27c08f6 100644
--- a/apps/ios/Shared/Views/Chat/Group/MemberSupportView.swift
+++ b/apps/ios/Shared/Views/Chat/Group/MemberSupportView.swift
@@ -196,7 +196,9 @@ struct MemberSupportView: View {
}
private func memberStatus(_ member: GroupMember) -> LocalizedStringKey {
- if member.activeConn?.connDisabled ?? false {
+ if case .failed = member.activeConn?.connStatus {
+ return "failed"
+ } else if member.activeConn?.connDisabled ?? false {
return "disabled"
} else if member.activeConn?.connInactive ?? false {
return "inactive"
diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift
index 4937bca20e..381057db5b 100644
--- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift
+++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift
@@ -40,6 +40,7 @@ func dynamicSize(_ font: DynamicTypeSize) -> DynamicSizes {
dynamicSizes[font] ?? defaultDynamicSizes
}
+// Spec: spec/client/chat-list.md#ChatListNavLink
struct ChatListNavLink: View {
@EnvironmentObject var chatModel: ChatModel
@EnvironmentObject var theme: AppTheme
@@ -90,6 +91,7 @@ struct ChatListNavLink: View {
.actionSheet(item: $actionSheet) { $0.actionSheet }
}
+ // Spec: spec/client/chat-list.md#contactNavLink
private func contactNavLink(_ contact: Contact) -> some View {
Group {
if contact.isContactCard {
@@ -211,6 +213,7 @@ struct ChatListNavLink: View {
}
}
+ // Spec: spec/client/chat-list.md#groupNavLink
@ViewBuilder private func groupNavLink(_ groupInfo: GroupInfo) -> some View {
switch (groupInfo.membership.memberStatus) {
case .memInvited:
@@ -295,6 +298,7 @@ struct ChatListNavLink: View {
}
}
+ // Spec: spec/client/chat-list.md#noteFolderNavLink
private func noteFolderNavLink(_ noteFolder: NoteFolder) -> some View {
NavLinkPlain(
chatId: chat.chatInfo.id,
@@ -325,6 +329,7 @@ struct ChatListNavLink: View {
.tint(chat.chatInfo.incognito ? .indigo : theme.colors.primary)
}
+ // Spec: spec/client/chat-list.md#markReadButton
@ViewBuilder private func markReadButton() -> some View {
if chat.chatStats.unreadCount > 0 || chat.chatStats.unreadChat {
Button {
@@ -344,6 +349,7 @@ struct ChatListNavLink: View {
}
+ // Spec: spec/client/chat-list.md#toggleFavoriteButton
@ViewBuilder private func toggleFavoriteButton() -> some View {
if chat.chatInfo.chatSettings?.favorite == true {
Button {
@@ -362,6 +368,7 @@ struct ChatListNavLink: View {
}
}
+ // Spec: spec/client/chat-list.md#toggleNtfsButton
@ViewBuilder private func toggleNtfsButton(chat: Chat) -> some View {
if let nextMode = chat.chatInfo.nextNtfMode {
Button {
@@ -382,6 +389,7 @@ struct ChatListNavLink: View {
}
}
+ // Spec: spec/client/chat-list.md#clearChatButton
private func clearChatButton() -> some View {
Button {
AlertManager.shared.showAlert(clearChatAlert())
@@ -483,6 +491,7 @@ struct ChatListNavLink: View {
.tint(.red)
}
+ // Spec: spec/client/chat-list.md#contactRequestNavLink
private func contactRequestNavLink(_ contactRequest: UserContactRequest) -> some View {
ContactRequestView(contactRequest: contactRequest, chat: chat)
.frameCompat(height: dynamicRowHeight)
@@ -517,6 +526,7 @@ struct ChatListNavLink: View {
}
}
+ // Spec: spec/client/chat-list.md#contactConnectionNavLink
private func contactConnectionNavLink(_ contactConnection: PendingContactConnection) -> some View {
ContactConnectionView(chat: chat)
.frameCompat(height: dynamicRowHeight)
diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift
index efaba518a9..d84fa29c81 100644
--- a/apps/ios/Shared/Views/ChatList/ChatListView.swift
+++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift
@@ -5,6 +5,7 @@
// Created by Evgeny Poberezkin on 27/01/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/chat-list.md
import SwiftUI
import SimpleXChat
@@ -31,6 +32,7 @@ enum UserPickerSheet: Identifiable {
}
}
+// Spec: spec/client/chat-list.md#PresetTag
enum PresetTag: Int, Identifiable, CaseIterable, Equatable {
case groupReports = 0
case favorites = 1
@@ -46,6 +48,7 @@ enum PresetTag: Int, Identifiable, CaseIterable, Equatable {
}
}
+// Spec: spec/client/chat-list.md#ActiveFilter
enum ActiveFilter: Identifiable, Equatable {
case presetTag(PresetTag)
case userTag(ChatTag)
@@ -135,6 +138,7 @@ struct UserPickerSheetView: View {
}
}
+// Spec: spec/client/chat-list.md#ChatListView
struct ChatListView: View {
@EnvironmentObject var chatModel: ChatModel
@StateObject private var connectProgressManager = ConnectProgressManager.shared
@@ -160,6 +164,7 @@ struct ChatListView: View {
@AppStorage(DEFAULT_ADDRESS_CREATION_CARD_SHOWN) private var addressCreationCardShown = false
@AppStorage(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial
+ // Spec: spec/client/chat-list.md#body
var body: some View {
if #available(iOS 16.0, *) {
viewBody.scrollDismissesKeyboard(.immediately)
@@ -445,6 +450,7 @@ struct ChatListView: View {
}
+ // Spec: spec/client/chat-list.md#unreadBadge
private func unreadBadge(size: CGFloat = 18) -> some View {
Circle()
.frame(width: size, height: size)
@@ -464,11 +470,13 @@ struct ChatListView: View {
}
}
+ // Spec: spec/client/chat-list.md#stopAudioPlayer
func stopAudioPlayer() {
VoiceItemState.smallView.values.forEach { $0.audioPlayer?.stop() }
VoiceItemState.smallView = [:]
}
+ // Spec: spec/client/chat-list.md#filteredChats
private func filteredChats() -> [Chat] {
if let linkChatId = searchChatFilteredBySimplexLink {
return chatModel.chats.filter { $0.id == linkChatId }
@@ -511,6 +519,7 @@ struct ChatListView: View {
}
}
+ // Spec: spec/client/chat-list.md#searchString
func searchString() -> String {
searchShowingSimplexLink ? "" : searchText.trimmingCharacters(in: .whitespaces).localizedLowercase
}
@@ -574,6 +583,7 @@ struct SubsStatusIndicator: View {
}
}
+// Spec: spec/client/chat-list.md#ChatListSearchBar
struct ChatListSearchBar: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
@@ -875,6 +885,7 @@ struct TagsView: View {
}
}
+ // Spec: spec/client/chat-list.md#setActiveFilter
private func setActiveFilter(filter: ActiveFilter) {
if filter != chatTagsModel.activeFilter {
chatTagsModel.activeFilter = filter
@@ -895,6 +906,7 @@ func chatStoppedIcon() -> some View {
}
}
+// Spec: spec/client/chat-list.md#presetTagMatchesChat
func presetTagMatchesChat(_ tag: PresetTag, _ chatInfo: ChatInfo, _ chatStats: ChatStats) -> Bool {
switch tag {
case .groupReports:
diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift
index be2c456802..112e4099c0 100644
--- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift
+++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift
@@ -9,6 +9,7 @@
import SwiftUI
import SimpleXChat
+// Spec: spec/client/chat-list.md#ChatPreviewView
struct ChatPreviewView: View {
@EnvironmentObject var chatModel: ChatModel
@EnvironmentObject var theme: AppTheme
diff --git a/apps/ios/Shared/Views/ChatList/TagListView.swift b/apps/ios/Shared/Views/ChatList/TagListView.swift
index 79d122eabf..f484ce8938 100644
--- a/apps/ios/Shared/Views/ChatList/TagListView.swift
+++ b/apps/ios/Shared/Views/ChatList/TagListView.swift
@@ -16,6 +16,7 @@ struct TagEditorNavParams {
let tagId: Int64?
}
+// Spec: spec/client/chat-list.md#TagListView
struct TagListView: View {
var chat: Chat? = nil
@Environment(\.dismiss) var dismiss: DismissAction
diff --git a/apps/ios/Shared/Views/ChatList/UserPicker.swift b/apps/ios/Shared/Views/ChatList/UserPicker.swift
index b1cd4015c6..63d28e3624 100644
--- a/apps/ios/Shared/Views/ChatList/UserPicker.swift
+++ b/apps/ios/Shared/Views/ChatList/UserPicker.swift
@@ -6,6 +6,7 @@
import SwiftUI
import SimpleXChat
+// Spec: spec/client/chat-list.md#UserPicker
struct UserPicker: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
diff --git a/apps/ios/Shared/Views/Database/DatabaseEncryptionView.swift b/apps/ios/Shared/Views/Database/DatabaseEncryptionView.swift
index 441a164f8a..dbc25e536f 100644
--- a/apps/ios/Shared/Views/Database/DatabaseEncryptionView.swift
+++ b/apps/ios/Shared/Views/Database/DatabaseEncryptionView.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 04/09/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/database.md
import SwiftUI
import SimpleXChat
@@ -33,6 +34,7 @@ enum DatabaseEncryptionAlert: Identifiable {
}
}
+// Spec: spec/database.md#DatabaseEncryptionView
struct DatabaseEncryptionView: View {
@EnvironmentObject private var m: ChatModel
@EnvironmentObject private var theme: AppTheme
diff --git a/apps/ios/Shared/Views/Database/DatabaseErrorView.swift b/apps/ios/Shared/Views/Database/DatabaseErrorView.swift
index 02a1b87826..9610b4a24d 100644
--- a/apps/ios/Shared/Views/Database/DatabaseErrorView.swift
+++ b/apps/ios/Shared/Views/Database/DatabaseErrorView.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 04/09/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/database.md
import SwiftUI
import SimpleXChat
diff --git a/apps/ios/Shared/Views/Database/DatabaseView.swift b/apps/ios/Shared/Views/Database/DatabaseView.swift
index a7e61b3105..d5d70abaea 100644
--- a/apps/ios/Shared/Views/Database/DatabaseView.swift
+++ b/apps/ios/Shared/Views/Database/DatabaseView.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 19/06/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/database.md
import SwiftUI
import SimpleXChat
@@ -41,6 +42,7 @@ enum DatabaseAlert: Identifiable {
}
}
+// Spec: spec/database.md#DatabaseView
struct DatabaseView: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
diff --git a/apps/ios/Shared/Views/Database/MigrateToAppGroupView.swift b/apps/ios/Shared/Views/Database/MigrateToAppGroupView.swift
index 79c0a42ae0..76bdc898d5 100644
--- a/apps/ios/Shared/Views/Database/MigrateToAppGroupView.swift
+++ b/apps/ios/Shared/Views/Database/MigrateToAppGroupView.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 20/06/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/database.md
import SwiftUI
import SimpleXChat
diff --git a/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift b/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift
index c21ff9be8b..36608c58d6 100644
--- a/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift
+++ b/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 10/04/2023.
// Copyright © 2023 SimpleX Chat. All rights reserved.
//
+// Spec: spec/architecture.md
import SwiftUI
import SimpleXChat
diff --git a/apps/ios/Shared/Views/LocalAuth/PasscodeEntry.swift b/apps/ios/Shared/Views/LocalAuth/PasscodeEntry.swift
index 4a6f8e7549..6df31b4d59 100644
--- a/apps/ios/Shared/Views/LocalAuth/PasscodeEntry.swift
+++ b/apps/ios/Shared/Views/LocalAuth/PasscodeEntry.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 10/04/2023.
// Copyright © 2023 SimpleX Chat. All rights reserved.
//
+// Spec: spec/architecture.md
import SwiftUI
diff --git a/apps/ios/Shared/Views/LocalAuth/PasscodeView.swift b/apps/ios/Shared/Views/LocalAuth/PasscodeView.swift
index ca30fa5ce8..046a3fd1fc 100644
--- a/apps/ios/Shared/Views/LocalAuth/PasscodeView.swift
+++ b/apps/ios/Shared/Views/LocalAuth/PasscodeView.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 11/04/2023.
// Copyright © 2023 SimpleX Chat. All rights reserved.
//
+// Spec: spec/architecture.md
import SwiftUI
diff --git a/apps/ios/Shared/Views/LocalAuth/SetAppPasscodeView.swift b/apps/ios/Shared/Views/LocalAuth/SetAppPasscodeView.swift
index 7ec3ee1a42..995b9f5b0d 100644
--- a/apps/ios/Shared/Views/LocalAuth/SetAppPasscodeView.swift
+++ b/apps/ios/Shared/Views/LocalAuth/SetAppPasscodeView.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 10/04/2023.
// Copyright © 2023 SimpleX Chat. All rights reserved.
//
+// Spec: spec/architecture.md
import SwiftUI
import SimpleXChat
diff --git a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift
index 0af8fa7ad8..2ff376701c 100644
--- a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift
+++ b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift
@@ -5,6 +5,7 @@
// Created by Avently on 14.02.2024.
// Copyright © 2024 SimpleX Chat. All rights reserved.
//
+// Spec: spec/database.md
import SwiftUI
import SimpleXChat
diff --git a/apps/ios/Shared/Views/Migration/MigrateToDevice.swift b/apps/ios/Shared/Views/Migration/MigrateToDevice.swift
index 93fe19cf33..a28acfcba1 100644
--- a/apps/ios/Shared/Views/Migration/MigrateToDevice.swift
+++ b/apps/ios/Shared/Views/Migration/MigrateToDevice.swift
@@ -5,6 +5,7 @@
// Created by Avently on 23.02.2024.
// Copyright © 2024 SimpleX Chat. All rights reserved.
//
+// Spec: spec/database.md
import SwiftUI
import SimpleXChat
diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift
index 3de1fdb972..71a155949b 100644
--- a/apps/ios/Shared/Views/NewChat/NewChatView.swift
+++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift
@@ -5,6 +5,7 @@
// Created by spaced4ndy on 28.11.2023.
// Copyright © 2023 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/navigation.md
import SwiftUI
import SimpleXChat
@@ -73,6 +74,7 @@ func showKeepInvitationAlert() {
ChatModel.shared.showingInvitation = nil
}
+// Spec: spec/client/navigation.md#NewChatView
struct NewChatView: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
@@ -1163,6 +1165,7 @@ private func showOpenKnownGroupAlert(
)
}
+// Spec: spec/client/navigation.md#planAndConnect
func planAndConnect(
_ shortOrFullLink: String,
theme: AppTheme,
diff --git a/apps/ios/Shared/Views/NewChat/QRCode.swift b/apps/ios/Shared/Views/NewChat/QRCode.swift
index c9054f30da..2b38065bd9 100644
--- a/apps/ios/Shared/Views/NewChat/QRCode.swift
+++ b/apps/ios/Shared/Views/NewChat/QRCode.swift
@@ -5,6 +5,7 @@
// Created by Evgeny Poberezkin on 30/01/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/navigation.md
import SwiftUI
import CoreImage.CIFilterBuiltins
diff --git a/apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift b/apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift
index c8d0faafa7..f22d59fcac 100644
--- a/apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift
+++ b/apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift
@@ -5,6 +5,7 @@
// Created by Diogo Cunha on 13/11/2024.
// Copyright © 2024 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/navigation.md
import SwiftUI
import SimpleXChat
diff --git a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift
index 33ffa04a50..b5598c1f85 100644
--- a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift
+++ b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift
@@ -5,6 +5,7 @@
// Created by spaced4ndy on 31.10.2024.
// Copyright © 2024 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/navigation.md
import SwiftUI
import SimpleXChat
diff --git a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift
index f119beec50..7301c0421d 100644
--- a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift
+++ b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 07/05/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/navigation.md
import SwiftUI
import SimpleXChat
diff --git a/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift b/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift
index 03b0fcba1a..ab84bed7df 100644
--- a/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift
+++ b/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift
@@ -5,6 +5,7 @@
// Created by spaced4ndy on 28.04.2023.
// Copyright © 2023 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/navigation.md
import SwiftUI
import Contacts
diff --git a/apps/ios/Shared/Views/Onboarding/HowItWorks.swift b/apps/ios/Shared/Views/Onboarding/HowItWorks.swift
index 7452d74e91..263b55a42d 100644
--- a/apps/ios/Shared/Views/Onboarding/HowItWorks.swift
+++ b/apps/ios/Shared/Views/Onboarding/HowItWorks.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 08/05/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/navigation.md
import SwiftUI
diff --git a/apps/ios/Shared/Views/Onboarding/OnboardingView.swift b/apps/ios/Shared/Views/Onboarding/OnboardingView.swift
index 8f448dc508..daef95fbc6 100644
--- a/apps/ios/Shared/Views/Onboarding/OnboardingView.swift
+++ b/apps/ios/Shared/Views/Onboarding/OnboardingView.swift
@@ -5,9 +5,11 @@
// Created by Evgeny on 07/05/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/navigation.md
import SwiftUI
+// Spec: spec/client/navigation.md#OnboardingView
struct OnboardingView: View {
var onboarding: OnboardingStage
@@ -40,6 +42,7 @@ func onboardingButtonPlaceholder() -> some View {
Spacer().frame(height: 40)
}
+// Spec: spec/client/navigation.md#onboardingStage
enum OnboardingStage: String, Identifiable {
case step1_SimpleXInfo
case step2_CreateProfile // deprecated
diff --git a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift
index 31865e7af9..717405b03b 100644
--- a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift
+++ b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 03/07/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/navigation.md
import SwiftUI
import SimpleXChat
diff --git a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift
index 9f41a37b1d..80f35c1190 100644
--- a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift
+++ b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 07/05/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/navigation.md
import SwiftUI
import SimpleXChat
diff --git a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift
index 916e3f9e78..8a7ab465d4 100644
--- a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift
+++ b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 24/12/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/navigation.md
import SwiftUI
import SimpleXChat
diff --git a/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift b/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift
index 02dec5a618..54a60eed19 100644
--- a/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift
+++ b/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 03/08/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/services/theme.md
import SwiftUI
import SimpleXChat
@@ -21,6 +22,7 @@ let darkThemesWithoutBlackNames: [String] = [DefaultTheme.DARK.themeName, Defaul
let appSettingsURL = URL(string: UIApplication.openSettingsURLString)!
+// Spec: spec/services/theme.md#AppearanceSettings
struct AppearanceSettings: View {
@EnvironmentObject var m: ChatModel
@Environment(\.colorScheme) var colorScheme
@@ -313,6 +315,7 @@ struct AppearanceSettings: View {
}
}
+// Spec: spec/services/theme.md#ToolbarMaterial
enum ToolbarMaterial: String, CaseIterable {
case bar
case ultraThin
@@ -596,6 +599,7 @@ struct CustomizeThemeView: View {
}
}
+// Spec: spec/services/theme.md#ImportExportThemeSection
struct ImportExportThemeSection: View {
@EnvironmentObject var theme: AppTheme
@Binding var showFileImporter: Bool
@@ -632,6 +636,7 @@ struct ImportExportThemeSection: View {
}
}
+// Spec: spec/services/theme.md#ThemeImporter
struct ThemeImporter: ViewModifier {
@Binding var isPresented: Bool
var save: (ThemeOverrides) -> Void
@@ -1141,6 +1146,7 @@ private func removeUserThemeModeOverrides(_ themeUserDestination: Binding<(Int64
wallpaperFilesToDelete.forEach(removeWallpaperFile)
}
+// Spec: spec/services/theme.md#decodeYAML
private func decodeYAML(_ string: String) -> T? {
do {
return try YAMLDecoder().decode(T.self, from: string)
@@ -1150,6 +1156,7 @@ private func decodeYAML(_ string: String) -> T? {
}
}
+// Spec: spec/services/theme.md#encodeThemeOverrides
private func encodeThemeOverrides(_ value: ThemeOverrides) throws -> String {
let encoder = YAMLEncoder()
encoder.options = YAMLEncoder.Options(sequenceStyle: .block, mappingStyle: .block, newLineScalarStyle: .doubleQuoted)
diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift
index 3a536c7b17..74d38b050b 100644
--- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift
+++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 02/08/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/architecture.md
import SwiftUI
import SimpleXChat
diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ConditionsWebView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ConditionsWebView.swift
index 1e38b7d5ec..6f76e69182 100644
--- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ConditionsWebView.swift
+++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ConditionsWebView.swift
@@ -5,6 +5,7 @@
// Created by Stanislav Dmitrenko on 26.11.2024.
// Copyright © 2024 SimpleX Chat. All rights reserved.
//
+// Spec: spec/architecture.md
import SwiftUI
import WebKit
diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift
index 6f4710396a..64e3d15de0 100644
--- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift
+++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 02/08/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/architecture.md
import SwiftUI
import SimpleXChat
diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NewServerView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NewServerView.swift
index c8cb2349e7..b44271bd89 100644
--- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NewServerView.swift
+++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NewServerView.swift
@@ -5,6 +5,7 @@
// Created by spaced4ndy on 13.11.2024.
// Copyright © 2024 SimpleX Chat. All rights reserved.
//
+// Spec: spec/architecture.md
import SwiftUI
import SimpleXChat
diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift
index afbccc109c..abd8be03b9 100644
--- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift
+++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift
@@ -5,6 +5,7 @@
// Created by spaced4ndy on 28.10.2024.
// Copyright © 2024 SimpleX Chat. All rights reserved.
//
+// Spec: spec/architecture.md
import SwiftUI
import SimpleXChat
diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift
index 97bfd360cb..97bf9ebc93 100644
--- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift
+++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 15/11/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/architecture.md
import SwiftUI
import SimpleXChat
diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift
index b9737914ec..49e1ff79ea 100644
--- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift
+++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 15/11/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/architecture.md
import SwiftUI
import SimpleXChat
diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ScanProtocolServer.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ScanProtocolServer.swift
index b28b1a4d1e..fd29fd906e 100644
--- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ScanProtocolServer.swift
+++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ScanProtocolServer.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 19/11/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/architecture.md
import SwiftUI
import SimpleXChat
diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift
index cb6fdf8597..c091224098 100644
--- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift
+++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift
@@ -5,6 +5,7 @@
// Created by Evgeny Poberezkin on 31/01/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/navigation.md
import SwiftUI
import StoreKit
diff --git a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift
index ddfe59e719..ad3b5cdf95 100644
--- a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift
+++ b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift
@@ -2,6 +2,7 @@
// Created by Avently on 17.01.2023.
// Copyright (c) 2023 SimpleX Chat. All rights reserved.
//
+// Spec: spec/client/navigation.md
import SwiftUI
import SimpleXChat
diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff
index a59179ddfa..3c21db94b6 100644
--- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff
+++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff
@@ -792,6 +792,10 @@ swipe action
Всички членове на групата ще останат свързани.
No comment provided by engineer.
+
+ All messages
+ No comment provided by engineer.
+
All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.
Всички съобщения и файлове се изпращат с **криптиране от край до край**, с постквантова сигурност в директните съобщения.
@@ -1142,6 +1146,10 @@ swipe action
Аудио и видео разговори
No comment provided by engineer.
+
+ Audio call
+ No comment provided by engineer.
+
Audio/video calls
Аудио/видео разговори
@@ -2552,6 +2560,14 @@ swipe action
Изтрий съобщението на члена?
No comment provided by engineer.
+
+ Delete member messages
+ No comment provided by engineer.
+
+
+ Delete member messages?
+ alert title
+
Delete message?
Изтрий съобщението?
@@ -2560,7 +2576,8 @@ swipe action
Delete messages
Изтрий съобщенията
- alert button
+ alert action
+alert button
Delete messages after
@@ -3741,6 +3758,10 @@ snd error text
Файловете и медията са забранени!
No comment provided by engineer.
+
+ Filter
+ No comment provided by engineer.
+
Filter unread and favorite chats.
Филтрирайте непрочетените и любимите чатове.
@@ -4194,6 +4215,10 @@ Error: %2$@
Изображението ще бъде получено, когато вашият контакт е онлайн, моля, изчакайте или проверете по-късно!
No comment provided by engineer.
+
+ Images
+ No comment provided by engineer.
+
Immediately
Веднага
@@ -4442,6 +4467,10 @@ More improvements are coming soon!
Покани приятели
No comment provided by engineer.
+
+ Invite member
+ No comment provided by engineer.
+
Invite members
Покани членове
@@ -4658,6 +4687,10 @@ This is your link for group %@!
Запомнени настолни устройства
No comment provided by engineer.
+
+ Links
+ No comment provided by engineer.
+
List
swipe action
@@ -4773,6 +4806,10 @@ This is your link for group %@!
Member is deleted - can't accept request
No comment provided by engineer.
+
+ Member messages will be deleted - this cannot be undone!
+ alert message
+
Member reports
chat feature
@@ -4793,12 +4830,12 @@ This is your link for group %@!
Member will be removed from chat - this cannot be undone!
- No comment provided by engineer.
+ alert message
Member will be removed from group - this cannot be undone!
Членът ще бъде премахнат от групата - това не може да бъде отменено!
- No comment provided by engineer.
+ alert message
Member will join the group, accept member?
@@ -6296,7 +6333,11 @@ swipe action
Remove
Премахване
- No comment provided by engineer.
+ alert action
+
+
+ Remove and delete messages
+ alert action
Remove archive?
@@ -6318,7 +6359,7 @@ swipe action
Remove member?
Острани член?
- No comment provided by engineer.
+ alert title
Remove passphrase from keychain?
@@ -6703,11 +6744,31 @@ chat item action
Лентата за търсене приема линк за връзка.
No comment provided by engineer.
+
+ Search files
+ No comment provided by engineer.
+
+
+ Search images
+ No comment provided by engineer.
+
+
+ Search links
+ No comment provided by engineer.
+
Search or paste SimpleX link
Търсене или поставяне на SimpleX линк
No comment provided by engineer.
+
+ Search videos
+ No comment provided by engineer.
+
+
+ Search voice messages
+ No comment provided by engineer.
+
Secondary
No comment provided by engineer.
@@ -8432,6 +8493,10 @@ To connect, please ask your contact to create another connection link and check
Видеото ще бъде получено, когато вашият контакт е онлайн, моля, изчакайте или проверете по-късно!
No comment provided by engineer.
+
+ Videos
+ No comment provided by engineer.
+
Videos and files up to 1gb
Видео и файлове до 1gb
diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff
index ace3079550..db9c2e1910 100644
--- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff
+++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff
@@ -782,6 +782,10 @@ swipe action
Všichni členové skupiny zůstanou připojeni.
No comment provided by engineer.
+
+ All messages
+ No comment provided by engineer.
+
All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.
No comment provided by engineer.
@@ -1117,6 +1121,10 @@ swipe action
Hlasové a video hovory
No comment provided by engineer.
+
+ Audio call
+ No comment provided by engineer.
+
Audio/video calls
Audio/video hovory
@@ -2438,6 +2446,14 @@ swipe action
Smazat zprávu člena?
No comment provided by engineer.
+
+ Delete member messages
+ No comment provided by engineer.
+
+
+ Delete member messages?
+ alert title
+
Delete message?
Smazat zprávu?
@@ -2446,7 +2462,8 @@ swipe action
Delete messages
Smazat zprávy
- alert button
+ alert action
+alert button
Delete messages after
@@ -3596,6 +3613,10 @@ snd error text
Soubory a média jsou zakázány!
No comment provided by engineer.
+
+ Filter
+ No comment provided by engineer.
+
Filter unread and favorite chats.
Filtrovat nepřečtené a oblíbené chaty.
@@ -4037,6 +4058,10 @@ Error: %2$@
Obrázek bude přijat, až bude váš kontakt online, vyčkejte prosím nebo se podívejte později!
No comment provided by engineer.
+
+ Images
+ No comment provided by engineer.
+
Immediately
Ihned
@@ -4272,6 +4297,10 @@ More improvements are coming soon!
Pozvat přátele
No comment provided by engineer.
+
+ Invite member
+ No comment provided by engineer.
+
Invite members
Pozvat členy
@@ -4479,6 +4508,10 @@ This is your link for group %@!
Linked desktops
No comment provided by engineer.
+
+ Links
+ No comment provided by engineer.
+
List
swipe action
@@ -4594,6 +4627,10 @@ This is your link for group %@!
Member is deleted - can't accept request
No comment provided by engineer.
+
+ Member messages will be deleted - this cannot be undone!
+ alert message
+
Member reports
chat feature
@@ -4614,12 +4651,12 @@ This is your link for group %@!
Member will be removed from chat - this cannot be undone!
- No comment provided by engineer.
+ alert message
Member will be removed from group - this cannot be undone!
Člen bude odstraněn ze skupiny - toto nelze vzít zpět!
- No comment provided by engineer.
+ alert message
Member will join the group, accept member?
@@ -6074,7 +6111,11 @@ swipe action
Remove
Odstranit
- No comment provided by engineer.
+ alert action
+
+
+ Remove and delete messages
+ alert action
Remove archive?
@@ -6096,7 +6137,7 @@ swipe action
Remove member?
Odebrat člena?
- No comment provided by engineer.
+ alert title
Remove passphrase from keychain?
@@ -6471,10 +6512,30 @@ chat item action
Search bar accepts invitation links.
No comment provided by engineer.
+
+ Search files
+ No comment provided by engineer.
+
+
+ Search images
+ No comment provided by engineer.
+
+
+ Search links
+ No comment provided by engineer.
+
Search or paste SimpleX link
No comment provided by engineer.
+
+ Search videos
+ No comment provided by engineer.
+
+
+ Search voice messages
+ No comment provided by engineer.
+
Secondary
No comment provided by engineer.
@@ -8156,6 +8217,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
Video obdržíte, až bude váš kontakt online, vyčkejte prosím nebo zkontrolujte později!
No comment provided by engineer.
+
+ Videos
+ No comment provided by engineer.
+
Videos and files up to 1gb
Videa a soubory až do velikosti 1 gb
diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff
index 34bbab2a6a..cec79a1739 100644
--- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff
+++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff
@@ -792,6 +792,10 @@ swipe action
Alle Gruppenmitglieder bleiben verbunden.
No comment provided by engineer.
+
+ All messages
+ No comment provided by engineer.
+
All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.
Alle Nachrichten und Dateien werden **Ende-zu-Ende verschlüsselt** versendet - in Direkt-Nachrichten mit Post-Quantum-Security.
@@ -1142,6 +1146,10 @@ swipe action
Audio- und Videoanrufe
No comment provided by engineer.
+
+ Audio call
+ No comment provided by engineer.
+
Audio/video calls
Audio-/Video-Anrufe
@@ -2579,6 +2587,14 @@ swipe action
Nachricht des Mitglieds löschen?
No comment provided by engineer.
+
+ Delete member messages
+ No comment provided by engineer.
+
+
+ Delete member messages?
+ alert title
+
Delete message?
Die Nachricht löschen?
@@ -2587,7 +2603,8 @@ swipe action
Delete messages
Nachrichten löschen
- alert button
+ alert action
+alert button
Delete messages after
@@ -3851,6 +3868,10 @@ snd error text
Dateien und Medien sind nicht erlaubt!
No comment provided by engineer.
+
+ Filter
+ No comment provided by engineer.
+
Filter unread and favorite chats.
Nach ungelesenen und favorisierten Chats filtern.
@@ -4335,6 +4356,10 @@ Fehler: %2$@
Das Bild wird heruntergeladen, sobald Ihr Kontakt online ist. Bitte warten oder schauen Sie später nochmal nach!
No comment provided by engineer.
+
+ Images
+ No comment provided by engineer.
+
Immediately
Sofort
@@ -4594,6 +4619,10 @@ Weitere Verbesserungen sind bald verfügbar!
Freunde einladen
No comment provided by engineer.
+
+ Invite member
+ No comment provided by engineer.
+
Invite members
Mitglieder einladen
@@ -4817,6 +4846,10 @@ Das ist Ihr Link für die Gruppe %@!
Verknüpfte Desktops
No comment provided by engineer.
+
+ Links
+ No comment provided by engineer.
+
List
Liste
@@ -4942,6 +4975,10 @@ Das ist Ihr Link für die Gruppe %@!
Mitglied ist gelöscht - Anfrage kann nicht angenommen werden
No comment provided by engineer.
+
+ Member messages will be deleted - this cannot be undone!
+ alert message
+
Member reports
Mitglieder-Meldungen
@@ -4965,12 +5002,12 @@ Das ist Ihr Link für die Gruppe %@!
Member will be removed from chat - this cannot be undone!
Das Mitglied wird aus dem Chat entfernt. Dies kann nicht rückgängig gemacht werden!
- No comment provided by engineer.
+ alert message
Member will be removed from group - this cannot be undone!
Das Mitglied wird aus der Gruppe entfernt. Dies kann nicht rückgängig gemacht werden!
- No comment provided by engineer.
+ alert message
Member will join the group, accept member?
@@ -6595,7 +6632,11 @@ swipe action
Remove
Entfernen
- No comment provided by engineer.
+ alert action
+
+
+ Remove and delete messages
+ alert action
Remove archive?
@@ -6620,7 +6661,7 @@ swipe action
Remove member?
Das Mitglied entfernen?
- No comment provided by engineer.
+ alert title
Remove passphrase from keychain?
@@ -7038,11 +7079,31 @@ chat item action
In der Suchleiste werden nun auch Einladungslinks angenommen.
No comment provided by engineer.
+
+ Search files
+ No comment provided by engineer.
+
+
+ Search images
+ No comment provided by engineer.
+
+
+ Search links
+ No comment provided by engineer.
+
Search or paste SimpleX link
Suchen oder SimpleX-Link einfügen
No comment provided by engineer.
+
+ Search videos
+ No comment provided by engineer.
+
+
+ Search voice messages
+ No comment provided by engineer.
+
Secondary
Zweite Farbe
@@ -8918,6 +8979,10 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
Das Video wird heruntergeladen, sobald Ihr Kontakt online ist. Bitte warten oder überprüfen Sie es später!
No comment provided by engineer.
+
+ Videos
+ No comment provided by engineer.
+
Videos and files up to 1gb
Videos und Dateien bis zu 1GB
diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff
index 2f5a0acbb1..581cd791a5 100644
--- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff
+++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff
@@ -792,6 +792,11 @@ swipe action
All group members will remain connected.
No comment provided by engineer.
+
+ All messages
+ All messages
+ No comment provided by engineer.
+
All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.
All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.
@@ -1142,6 +1147,11 @@ swipe action
Audio and video calls
No comment provided by engineer.
+
+ Audio call
+ Audio call
+ No comment provided by engineer.
+
Audio/video calls
Audio/video calls
@@ -2579,6 +2589,16 @@ swipe action
Delete member message?
No comment provided by engineer.
+
+ Delete member messages
+ Delete member messages
+ No comment provided by engineer.
+
+
+ Delete member messages?
+ Delete member messages?
+ alert title
+
Delete message?
Delete message?
@@ -2587,7 +2607,8 @@ swipe action
Delete messages
Delete messages
- alert button
+ alert action
+alert button
Delete messages after
@@ -3851,6 +3872,11 @@ snd error text
Files and media prohibited!
No comment provided by engineer.
+
+ Filter
+ Filter
+ No comment provided by engineer.
+
Filter unread and favorite chats.
Filter unread and favorite chats.
@@ -4335,6 +4361,11 @@ Error: %2$@
Image will be received when your contact is online, please wait or check later!
No comment provided by engineer.
+
+ Images
+ Images
+ No comment provided by engineer.
+
Immediately
Immediately
@@ -4594,6 +4625,11 @@ More improvements are coming soon!
Invite friends
No comment provided by engineer.
+
+ Invite member
+ Invite member
+ No comment provided by engineer.
+
Invite members
Invite members
@@ -4817,6 +4853,11 @@ This is your link for group %@!
Linked desktops
No comment provided by engineer.
+
+ Links
+ Links
+ No comment provided by engineer.
+
List
List
@@ -4942,6 +4983,11 @@ This is your link for group %@!
Member is deleted - can't accept request
No comment provided by engineer.
+
+ Member messages will be deleted - this cannot be undone!
+ Member messages will be deleted - this cannot be undone!
+ alert message
+
Member reports
Member reports
@@ -4965,12 +5011,12 @@ This is your link for group %@!
Member will be removed from chat - this cannot be undone!
Member will be removed from chat - this cannot be undone!
- No comment provided by engineer.
+ alert message
Member will be removed from group - this cannot be undone!
Member will be removed from group - this cannot be undone!
- No comment provided by engineer.
+ alert message
Member will join the group, accept member?
@@ -6595,7 +6641,12 @@ swipe action
Remove
Remove
- No comment provided by engineer.
+ alert action
+
+
+ Remove and delete messages
+ Remove and delete messages
+ alert action
Remove archive?
@@ -6620,7 +6671,7 @@ swipe action
Remove member?
Remove member?
- No comment provided by engineer.
+ alert title
Remove passphrase from keychain?
@@ -7038,11 +7089,36 @@ chat item action
Search bar accepts invitation links.
No comment provided by engineer.
+
+ Search files
+ Search files
+ No comment provided by engineer.
+
+
+ Search images
+ Search images
+ No comment provided by engineer.
+
+
+ Search links
+ Search links
+ No comment provided by engineer.
+
Search or paste SimpleX link
Search or paste SimpleX link
No comment provided by engineer.
+
+ Search videos
+ Search videos
+ No comment provided by engineer.
+
+
+ Search voice messages
+ Search voice messages
+ No comment provided by engineer.
+
Secondary
Secondary
@@ -8918,6 +8994,11 @@ To connect, please ask your contact to create another connection link and check
Video will be received when your contact is online, please wait or check later!
No comment provided by engineer.
+
+ Videos
+ Videos
+ No comment provided by engineer.
+
Videos and files up to 1gb
Videos and files up to 1gb
diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff
index 30c69af755..edacbd8e56 100644
--- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff
+++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff
@@ -792,6 +792,10 @@ swipe action
Todos los miembros del grupo permanecerán conectados.
No comment provided by engineer.
+
+ All messages
+ No comment provided by engineer.
+
All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.
Todos los mensajes y archivos son enviados **cifrados de extremo a extremo** y con seguridad de cifrado postcuántico en mensajes directos.
@@ -1142,6 +1146,10 @@ swipe action
Llamadas y videollamadas
No comment provided by engineer.
+
+ Audio call
+ No comment provided by engineer.
+
Audio/video calls
Llamadas y videollamadas
@@ -2579,6 +2587,14 @@ swipe action
¿Eliminar el mensaje de miembro?
No comment provided by engineer.
+
+ Delete member messages
+ No comment provided by engineer.
+
+
+ Delete member messages?
+ alert title
+
Delete message?
¿Eliminar mensaje?
@@ -2587,7 +2603,8 @@ swipe action
Delete messages
Activar
- alert button
+ alert action
+alert button
Delete messages after
@@ -3851,6 +3868,10 @@ snd error text
¡Archivos y multimedia no permitidos!
No comment provided by engineer.
+
+ Filter
+ No comment provided by engineer.
+
Filter unread and favorite chats.
Filtra chats no leídos y favoritos.
@@ -4335,6 +4356,10 @@ Error: %2$@
La imagen se recibirá cuando el contacto esté en línea, ¡por favor espera o revisa más tarde!
No comment provided by engineer.
+
+ Images
+ No comment provided by engineer.
+
Immediately
Inmediatamente
@@ -4594,6 +4619,10 @@ More improvements are coming soon!
Invitar amigos
No comment provided by engineer.
+
+ Invite member
+ No comment provided by engineer.
+
Invite members
Invitar miembros
@@ -4817,6 +4846,10 @@ This is your link for group %@!
Ordenadores enlazados
No comment provided by engineer.
+
+ Links
+ No comment provided by engineer.
+
List
Lista
@@ -4942,6 +4975,10 @@ This is your link for group %@!
Miembro eliminado, no puede aceptar solicitudes
No comment provided by engineer.
+
+ Member messages will be deleted - this cannot be undone!
+ alert message
+
Member reports
Informes de miembros
@@ -4965,12 +5002,12 @@ This is your link for group %@!
Member will be removed from chat - this cannot be undone!
El miembro será eliminado del chat. ¡No puede deshacerse!
- No comment provided by engineer.
+ alert message
Member will be removed from group - this cannot be undone!
El miembro será expulsado del grupo. ¡No puede deshacerse!
- No comment provided by engineer.
+ alert message
Member will join the group, accept member?
@@ -5484,7 +5521,7 @@ This is your link for group %@!
No direct connection yet, message is forwarded by admin.
- Aún no hay conexión directa con este miembro, el mensaje es reenviado por el administrador.
+ Aún no hay conexión directa, los mensajes son reenviados por el administrador.
item status description
@@ -6595,7 +6632,11 @@ swipe action
Remove
Eliminar
- No comment provided by engineer.
+ alert action
+
+
+ Remove and delete messages
+ alert action
Remove archive?
@@ -6620,7 +6661,7 @@ swipe action
Remove member?
¿Expulsar miembro?
- No comment provided by engineer.
+ alert title
Remove passphrase from keychain?
@@ -7038,11 +7079,31 @@ chat item action
La barra de búsqueda acepta enlaces de invitación.
No comment provided by engineer.
+
+ Search files
+ No comment provided by engineer.
+
+
+ Search images
+ No comment provided by engineer.
+
+
+ Search links
+ No comment provided by engineer.
+
Search or paste SimpleX link
Buscar o pegar enlace SimpleX
No comment provided by engineer.
+
+ Search videos
+ No comment provided by engineer.
+
+
+ Search voice messages
+ No comment provided by engineer.
+
Secondary
Secundario
@@ -8918,6 +8979,10 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión
El vídeo se recibirá cuando el contacto esté en línea, por favor espera o revisa más tarde.
No comment provided by engineer.
+
+ Videos
+ No comment provided by engineer.
+
Videos and files up to 1gb
Vídeos y archivos de hasta 1Gb
@@ -9649,7 +9714,7 @@ Repeat connection request?
accepted you
- te ha aceptado
+ te ha admitido
rcv group event chat item
@@ -10623,7 +10688,7 @@ last received msg: %2$@
you accepted this member
- has aceptado al miembro
+ has admitido al miembro
snd group event chat item
diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff
index 56fa4a1485..00b4bca1d4 100644
--- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff
+++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff
@@ -726,6 +726,10 @@ swipe action
Kaikki ryhmän jäsenet pysyvät yhteydessä.
No comment provided by engineer.
+
+ All messages
+ No comment provided by engineer.
+
All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.
No comment provided by engineer.
@@ -1042,6 +1046,10 @@ swipe action
Ääni- ja videopuhelut
No comment provided by engineer.
+
+ Audio call
+ No comment provided by engineer.
+
Audio/video calls
Ääni/videopuhelut
@@ -2328,6 +2336,14 @@ swipe action
Poista jäsenviesti?
No comment provided by engineer.
+
+ Delete member messages
+ No comment provided by engineer.
+
+
+ Delete member messages?
+ alert title
+
Delete message?
Poista viesti?
@@ -2336,7 +2352,8 @@ swipe action
Delete messages
Poista viestit
- alert button
+ alert action
+alert button
Delete messages after
@@ -3483,6 +3500,10 @@ snd error text
Tiedostot ja media kielletty!
No comment provided by engineer.
+
+ Filter
+ No comment provided by engineer.
+
Filter unread and favorite chats.
Suodata lukemattomia- ja suosikkikeskusteluja.
@@ -3924,6 +3945,10 @@ Error: %2$@
Kuva vastaanotetaan, kun kontaktisi on verkossa, odota tai tarkista myöhemmin!
No comment provided by engineer.
+
+ Images
+ No comment provided by engineer.
+
Immediately
Heti
@@ -4159,6 +4184,10 @@ More improvements are coming soon!
Kutsu ystäviä
No comment provided by engineer.
+
+ Invite member
+ No comment provided by engineer.
+
Invite members
Kutsu jäseniä
@@ -4366,6 +4395,10 @@ This is your link for group %@!
Linked desktops
No comment provided by engineer.
+
+ Links
+ No comment provided by engineer.
+
List
swipe action
@@ -4481,6 +4514,10 @@ This is your link for group %@!
Member is deleted - can't accept request
No comment provided by engineer.
+
+ Member messages will be deleted - this cannot be undone!
+ alert message
+
Member reports
chat feature
@@ -4501,12 +4538,12 @@ This is your link for group %@!
Member will be removed from chat - this cannot be undone!
- No comment provided by engineer.
+ alert message
Member will be removed from group - this cannot be undone!
Jäsen poistetaan ryhmästä - tätä ei voi perua!
- No comment provided by engineer.
+ alert message
Member will join the group, accept member?
@@ -5959,7 +5996,11 @@ swipe action
Remove
Poista
- No comment provided by engineer.
+ alert action
+
+
+ Remove and delete messages
+ alert action
Remove archive?
@@ -5981,7 +6022,7 @@ swipe action
Remove member?
Poista jäsen?
- No comment provided by engineer.
+ alert title
Remove passphrase from keychain?
@@ -6356,10 +6397,30 @@ chat item action
Search bar accepts invitation links.
No comment provided by engineer.
+
+ Search files
+ No comment provided by engineer.
+
+
+ Search images
+ No comment provided by engineer.
+
+
+ Search links
+ No comment provided by engineer.
+
Search or paste SimpleX link
No comment provided by engineer.
+
+ Search videos
+ No comment provided by engineer.
+
+
+ Search voice messages
+ No comment provided by engineer.
+
Secondary
No comment provided by engineer.
@@ -8038,6 +8099,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
Video vastaanotetaan, kun kontaktisi on online-tilassa, odota tai tarkista myöhemmin!
No comment provided by engineer.
+
+ Videos
+ No comment provided by engineer.
+
Videos and files up to 1gb
Videot ja tiedostot 1 Gt asti
diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff
index 67485353d2..82912c5d44 100644
--- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff
+++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff
@@ -792,6 +792,10 @@ swipe action
Tous les membres du groupe resteront connectés.
No comment provided by engineer.
+
+ All messages
+ No comment provided by engineer.
+
All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.
Tous les messages et fichiers sont envoyés **chiffrés de bout en bout**, avec une sécurité post-quantique dans les messages directs.
@@ -1141,6 +1145,10 @@ swipe action
Appels audio et vidéo
No comment provided by engineer.
+
+ Audio call
+ No comment provided by engineer.
+
Audio/video calls
Appels audio/vidéo
@@ -2564,6 +2572,14 @@ swipe action
Supprimer le message de ce membre ?
No comment provided by engineer.
+
+ Delete member messages
+ No comment provided by engineer.
+
+
+ Delete member messages?
+ alert title
+
Delete message?
Supprimer le message ?
@@ -2572,7 +2588,8 @@ swipe action
Delete messages
Supprimer les messages
- alert button
+ alert action
+alert button
Delete messages after
@@ -3822,6 +3839,10 @@ snd error text
Fichiers et médias interdits !
No comment provided by engineer.
+
+ Filter
+ No comment provided by engineer.
+
Filter unread and favorite chats.
Filtrer les messages non lus et favoris.
@@ -4296,6 +4317,10 @@ Erreur : %2$@
L'image sera reçue quand votre contact sera en ligne, merci d'attendre ou de revenir plus tard !
No comment provided by engineer.
+
+ Images
+ No comment provided by engineer.
+
Immediately
Immédiatement
@@ -4548,6 +4573,10 @@ D'autres améliorations sont à venir !
Inviter des amis
No comment provided by engineer.
+
+ Invite member
+ No comment provided by engineer.
+
Invite members
Inviter des membres
@@ -4769,6 +4798,10 @@ Voici votre lien pour le groupe %@ !
Bureaux liés
No comment provided by engineer.
+
+ Links
+ No comment provided by engineer.
+
List
swipe action
@@ -4887,6 +4920,10 @@ Voici votre lien pour le groupe %@ !
Member is deleted - can't accept request
No comment provided by engineer.
+
+ Member messages will be deleted - this cannot be undone!
+ alert message
+
Member reports
chat feature
@@ -4909,12 +4946,12 @@ Voici votre lien pour le groupe %@ !
Member will be removed from chat - this cannot be undone!
Le membre sera retiré de la discussion - cela ne peut pas être annulé !
- No comment provided by engineer.
+ alert message
Member will be removed from group - this cannot be undone!
Ce membre sera retiré du groupe - impossible de revenir en arrière !
- No comment provided by engineer.
+ alert message
Member will join the group, accept member?
@@ -6489,7 +6526,11 @@ swipe action
Remove
Supprimer
- No comment provided by engineer.
+ alert action
+
+
+ Remove and delete messages
+ alert action
Remove archive?
@@ -6513,7 +6554,7 @@ swipe action
Remove member?
Retirer ce membre ?
- No comment provided by engineer.
+ alert title
Remove passphrase from keychain?
@@ -6912,11 +6953,31 @@ chat item action
La barre de recherche accepte les liens d'invitation.
No comment provided by engineer.
+
+ Search files
+ No comment provided by engineer.
+
+
+ Search images
+ No comment provided by engineer.
+
+
+ Search links
+ No comment provided by engineer.
+
Search or paste SimpleX link
Rechercher ou coller un lien SimpleX
No comment provided by engineer.
+
+ Search videos
+ No comment provided by engineer.
+
+
+ Search voice messages
+ No comment provided by engineer.
+
Secondary
Secondaire
@@ -8743,6 +8804,10 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien
La vidéo ne sera reçue que lorsque votre contact sera en ligne. Veuillez patienter ou vérifier plus tard !
No comment provided by engineer.
+
+ Videos
+ No comment provided by engineer.
+
Videos and files up to 1gb
Vidéos et fichiers jusqu'à 1Go
diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff
index 22d223ffd9..99219c1f40 100644
--- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff
+++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff
@@ -92,12 +92,12 @@
%@ is not verified
- %@ nincs hitelesítve
+ %@ nincs ellenőrizve
No comment provided by engineer.
%@ is verified
- %@ hitelesítve
+ %@ ellenőrizve
No comment provided by engineer.
@@ -217,7 +217,7 @@
%lld contact(s) selected
- %lld partner kijelölve
+ %lld partner kiválasztva
No comment provided by engineer.
@@ -367,7 +367,7 @@
**Warning**: Instant push notifications require passphrase saved in Keychain.
- **Figyelmeztetés:** Az azonnali push-értesítésekhez a kulcstartóban tárolt jelmondat megadása szükséges.
+ **Figyelmeztetés:** Az azonnali leküldéses értesítésekhez a kulcstartóban tárolt jelmondat megadása szükséges.
No comment provided by engineer.
@@ -377,12 +377,12 @@
**e2e encrypted** audio call
- **e2e titkosított** hanghívás
+ **végpontok között titkosított** hanghívás
No comment provided by engineer.
**e2e encrypted** video call
- **e2e titkosított** videóhívás
+ **végpontok között titkosított** videóhívás
No comment provided by engineer.
@@ -789,7 +789,11 @@ swipe action
All group members will remain connected.
- Az összes csoporttag kapcsolatban marad.
+ Az összes csoporttag továbbra is kapcsolatban marad.
+ No comment provided by engineer.
+
+
+ All messages
No comment provided by engineer.
@@ -829,12 +833,12 @@ swipe action
All your contacts will remain connected.
- Az összes partnerével kapcsolatban marad.
+ Az összes partnerével továbbra is kapcsolatban marad.
No comment provided by engineer.
All your contacts will remain connected. Profile update will be sent to your contacts.
- A partnereivel kapcsolatban marad. A profilfrissítés el lesz küldve a partnerei számára.
+ Az összes partnerével továbbra is kapcsolatban marad. A profilfrissítés el lesz küldve a partnerei számára.
No comment provided by engineer.
@@ -954,7 +958,7 @@ swipe action
Allow your contacts to send disappearing messages.
- Az eltűnő üzenetek küldésének engedélyezése a partnerei számára.
+ Az eltűnő üzenetek küldése engedélyezve van a partnerei számára.
No comment provided by engineer.
@@ -984,12 +988,12 @@ swipe action
Always use private routing.
- Mindig használjon privát útválasztást.
+ Mindig legyen használva privát útválasztás.
No comment provided by engineer.
Always use relay
- Mindig használjon továbbítókiszolgálót
+ Mindig legyen használva továbbítókiszolgáló
No comment provided by engineer.
@@ -1142,6 +1146,10 @@ swipe action
Hang- és videóhívások
No comment provided by engineer.
+
+ Audio call
+ No comment provided by engineer.
+
Audio/video calls
Hang- és videóhívások
@@ -1264,12 +1272,12 @@ swipe action
Bio
- Névjegy
+ Életrajz
No comment provided by engineer.
Bio too large
- A névjegy túl hosszú
+ Az életrajz túl hosszú
alert title
@@ -1398,7 +1406,7 @@ swipe action
Call already ended!
- A hívás már befejeződött!
+ A hívás már véget ért!
No comment provided by engineer.
@@ -1691,7 +1699,7 @@ set passcode view
Choose _Migrate from another device_ on the new device and scan QR code.
- Válassza az _Átköltöztetés egy másik eszközről_ opciót az új eszközén és olvassa be a QR-kódot.
+ Válassza az _Átköltöztetés egy másik eszközről_ beállítást az új eszközén és olvassa be a QR-kódot.
No comment provided by engineer.
@@ -1721,37 +1729,37 @@ set passcode view
Clear
- Kiürítés
+ Ürítés
swipe action
Clear conversation
- Üzenetek kiürítése
+ Üzenetek ürítése
No comment provided by engineer.
Clear conversation?
- Kiüríti az üzeneteket?
+ Üríti a beszélgetés üzeneteit?
No comment provided by engineer.
Clear group?
- Kiüríti a csoportot?
+ Üríti a csoport üzeneteit?
No comment provided by engineer.
Clear or delete group?
- Csoport kiürítése vagy törlése?
+ Csoport ürítése vagy törlése?
No comment provided by engineer.
Clear private notes?
- Kiüríti a privát jegyzeteket?
+ Üríti a privát jegyzetek tartalmát?
No comment provided by engineer.
Clear verification
- Hitelesítés törlése
+ Ellenőrzés törlése
No comment provided by engineer.
@@ -1935,7 +1943,7 @@ Ez a saját egyszer használható meghívója!
Connect via one-time link
- Kapcsolódás egyszer használható meghívón keresztül
+ Kapcsolódás az egyszer használható meghívón keresztül
new chat sheet title
@@ -1960,7 +1968,7 @@ Ez a saját egyszer használható meghívója!
Connected to desktop
- Kapcsolódva a számítógéphez
+ Társítva a számítógéppel
No comment provided by engineer.
@@ -1985,7 +1993,7 @@ Ez a saját egyszer használható meghívója!
Connecting to desktop
- Kapcsolódás a számítógéphez
+ Társítás számítógéppel
No comment provided by engineer.
@@ -2212,7 +2220,7 @@ Ez a saját egyszer használható meghívója!
Create new profile in [desktop app](https://simplex.chat/downloads/). 💻
- Új profil létrehozása a [számítógép-alkalmazásban](https://simplex.chat/downloads/). 💻
+ Új profil létrehozása a [számítógépes alkalmazásban](https://simplex.chat/downloads/). 💻
No comment provided by engineer.
@@ -2456,7 +2464,7 @@ swipe action
Delete all files
- Az összes fájl törlése
+ Összes fájl törlése
No comment provided by engineer.
@@ -2579,6 +2587,14 @@ swipe action
Törli a tag üzenetét?
No comment provided by engineer.
+
+ Delete member messages
+ No comment provided by engineer.
+
+
+ Delete member messages?
+ alert title
+
Delete message?
Törli az üzenetet?
@@ -2587,7 +2603,8 @@ swipe action
Delete messages
Üzenetek törlése
- alert button
+ alert action
+alert button
Delete messages after
@@ -2706,7 +2723,7 @@ swipe action
Desktop app version %@ is not compatible with this app.
- A számítógép-alkalmazás verziója (%@) nem kompatibilis ezzel az alkalmazással.
+ A számítógépes alkalmazás verziója (%@) nem kompatibilis ezzel az alkalmazással.
No comment provided by engineer.
@@ -2881,7 +2898,7 @@ swipe action
Do NOT use private routing.
- NE használjon privát útválasztást.
+ NE legyen használva privát útválasztás.
No comment provided by engineer.
@@ -2911,7 +2928,7 @@ swipe action
Don't enable
- Ne engedélyezze
+ Nem engedélyezem
No comment provided by engineer.
@@ -2921,7 +2938,7 @@ swipe action
Don't show again
- Ne mutasd újra
+ Ne jelenjen meg újra
alert action
@@ -2992,7 +3009,7 @@ chat item action
E2E encrypted notifications.
- Végpontok közötti titkosított értesítések.
+ Végpontok között titkosított értesítések.
No comment provided by engineer.
@@ -3102,7 +3119,7 @@ chat item action
Encrypt
- Titkosít
+ Titkosítás
No comment provided by engineer.
@@ -3632,7 +3649,7 @@ chat item action
Error verifying passphrase:
- Hiba történt a jelmondat hitelesítésekor:
+ Hiba történt a jelmondat ellenőrzésekor:
No comment provided by engineer.
@@ -3851,6 +3868,10 @@ snd error text
A fájlok és a médiatartalmak küldése le van tiltva!
No comment provided by engineer.
+
+ Filter
+ No comment provided by engineer.
+
Filter unread and favorite chats.
Olvasatlan és kedvenc csevegésekre való szűrés.
@@ -4272,7 +4293,7 @@ Hiba: %2$@
How to
- Hogyan
+ Útmutató
No comment provided by engineer.
@@ -4317,7 +4338,7 @@ Hiba: %2$@
If you need to use the chat now tap **Do it later** below (you will be offered to migrate the database when you restart the app).
- Ha most kell használnia a csevegést, koppintson alább a **Befejezés később** lehetőségre (az alkalmazás újraindításakor fel lesz ajánlva az adatbázis átköltöztetése).
+ Ha most kell használnia a csevegést, koppintson lentebb a **Befejezés később** beállításra (az alkalmazás újraindításakor fel lesz ajánlva az adatbázis átköltöztetése).
No comment provided by engineer.
@@ -4335,6 +4356,10 @@ Hiba: %2$@
A kép akkor érkezik meg, amikor a küldője elérhető lesz, várjon, vagy ellenőrizze később!
No comment provided by engineer.
+
+ Images
+ No comment provided by engineer.
+
Immediately
Azonnal
@@ -4500,7 +4525,7 @@ További fejlesztések hamarosan!
Instant push notifications will be hidden!
- Az azonnali push-értesítések el lesznek rejtve!
+ Az azonnali leküldéses értesítések el lesznek rejtve!
No comment provided by engineer.
@@ -4594,6 +4619,10 @@ További fejlesztések hamarosan!
Barátok meghívása
No comment provided by engineer.
+
+ Invite member
+ No comment provided by engineer.
+
Invite members
Tagok meghívása
@@ -4714,7 +4743,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!
Keep the app open to use it from desktop
- A számítógépről való használathoz tartsd nyitva az alkalmazást
+ Alkalmazás megnyitva tartása a számítógépről való használathoz
No comment provided by engineer.
@@ -4804,7 +4833,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!
Link mobile and desktop apps! 🔗
- Társítsa össze a hordozható eszköz- és számítógépes alkalmazásokat! 🔗
+ Társítsa össze a hordozható eszköz- és a számítógépes alkalmazásokat! 🔗
No comment provided by engineer.
@@ -4817,6 +4846,10 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!
Társított számítógépek
No comment provided by engineer.
+
+ Links
+ No comment provided by engineer.
+
List
Lista
@@ -4894,7 +4927,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!
Mark verified
- Hitelesítés
+ Megjelölés ellenőrzöttként
No comment provided by engineer.
@@ -4904,7 +4937,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!
Max 30 seconds, received instantly.
- Max. 30 másodperc, azonnal érkezett.
+ Legfeljebb 30 másodperc, azonnal megérkezik.
No comment provided by engineer.
@@ -4942,6 +4975,10 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!
A tag törölve lett – nem lehet elfogadni a kérést
No comment provided by engineer.
+
+ Member messages will be deleted - this cannot be undone!
+ alert message
+
Member reports
Tagok jelentései
@@ -4965,12 +5002,12 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!
Member will be removed from chat - this cannot be undone!
A tag el lesz távolítva a csevegésből – ez a művelet nem vonható vissza!
- No comment provided by engineer.
+ alert message
Member will be removed from group - this cannot be undone!
A tag el lesz távolítva a csoportból – ez a művelet nem vonható vissza!
- No comment provided by engineer.
+ alert message
Member will join the group, accept member?
@@ -5044,7 +5081,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!
Message draft
- Üzenetvázlat
+ Piszkozatok
No comment provided by engineer.
@@ -5159,7 +5196,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!
Messages were deleted after you selected them.
- Az üzeneteket törölték miután kijelölte őket.
+ Az üzeneteket törölték miután kiváasztotta őket.
alert message
@@ -5219,7 +5256,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!
Migration failed. Tap **Skip** below to continue using the current database. Please report the issue to the app developers via chat or email [chat@simplex.chat](mailto:chat@simplex.chat).
- Sikertelen átköltöztetés. Koppintson a **Kihagyás** lehetőségre a jelenlegi adatbázis használatának folytatásához. Jelentse a problémát az alkalmazás fejlesztőinek csevegésben vagy e-mailben [chat@simplex.chat](mailto:chat@simplex.chat).
+ Sikertelen átköltöztetés. Koppintson a **Kihagyás** beállításra a jelenlegi adatbázis használatának folytatásához. Jelentse a problémát az alkalmazás fejlesztőinek csevegésben vagy e-mailben [chat@simplex.chat](mailto:chat@simplex.chat).
No comment provided by engineer.
@@ -5374,12 +5411,12 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!
New contact:
- Új kapcsolat:
+ Új partner:
notification
New desktop app!
- Új számítógép-alkalmazás!
+ Új számítógépes alkalmazás!
No comment provided by engineer.
@@ -5464,7 +5501,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!
No contacts selected
- Nincs partner kijelölve
+ Nincs partner kiválasztva
No comment provided by engineer.
@@ -5549,7 +5586,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!
No push server
- Helyi
+ Nincs kiszolgáló a leküldéses értesítésekhez
No comment provided by engineer.
@@ -5604,7 +5641,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!
Nothing selected
- Nincs semmi kijelölve
+ Nincs semmi kiválasztva
No comment provided by engineer.
@@ -5739,17 +5776,17 @@ VPN engedélyezése szükséges.
Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)
- Véglegesen csak Ön törölhet üzeneteket (partnere csak törlésre jelölheti meg őket ). (24 óra)
+ Csak Ön törölheti véglegesen az üzeneteket (partnere csak törlésre jelölheti meg azokat ). (24 óra)
No comment provided by engineer.
Only you can make calls.
- Csak Ön tud hívásokat indítani.
+ Csak Ön kezdeményezhet hívásokat.
No comment provided by engineer.
Only you can send disappearing messages.
- Csak Ön tud eltűnő üzeneteket küldeni.
+ Csak Ön küldhet eltűnő üzeneteket.
No comment provided by engineer.
@@ -5759,7 +5796,7 @@ VPN engedélyezése szükséges.
Only you can send voice messages.
- Csak Ön tud hangüzeneteket küldeni.
+ Csak Ön küldhet hangüzeneteket.
No comment provided by engineer.
@@ -5769,17 +5806,17 @@ VPN engedélyezése szükséges.
Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours)
- Csak a partnere tudja az üzeneteket véglegesen törölni (Ön csak törlésre jelölheti meg azokat). (24 óra)
+ Csak a partnere törölheti véglegesen az üzeneteket (Ön csak törlésre jelölheti meg azokat). (24 óra)
No comment provided by engineer.
Only your contact can make calls.
- Csak a partnere tud hívást indítani.
+ Csak a partnere kezdeményezhet hívásokat.
No comment provided by engineer.
Only your contact can send disappearing messages.
- Csak a partnere tud eltűnő üzeneteket küldeni.
+ Csak a partnere küldhet eltűnő üzeneteket.
No comment provided by engineer.
@@ -5789,7 +5826,7 @@ VPN engedélyezése szükséges.
Only your contact can send voice messages.
- Csak a partnere tud hangüzeneteket küldeni.
+ Csak a partnere küldhet hangüzeneteket.
No comment provided by engineer.
@@ -6100,7 +6137,7 @@ Hiba: %@
Please restart the app and migrate the database to enable push notifications.
- Indítsa újra az alkalmazást az adatbázis-átköltöztetéséhez szükséges push-értesítések engedélyezéséhez.
+ Indítsa újra az alkalmazást az adatbázis-átköltöztetéséhez szükséges leküldéses értesítések engedélyezéséhez.
No comment provided by engineer.
@@ -6285,7 +6322,7 @@ Hiba: %@
Prohibit reporting messages to moderators.
- Az üzenetek a moderátorok felé történő jelentésének megtiltása.
+ Az üzenetek jelentése a moderátorok felé le van tiltva.
No comment provided by engineer.
@@ -6367,12 +6404,12 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben.
Push notifications
- Push-értesítések
+ Leküldéses értesítések
No comment provided by engineer.
Push server
- Push-kiszolgáló
+ Leküldéses értesítéskiszolgáló
No comment provided by engineer.
@@ -6382,7 +6419,7 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben.
Rate the app
- Értékelje az alkalmazást
+ Alkalmazás értékelése
No comment provided by engineer.
@@ -6392,7 +6429,7 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben.
React…
- Reagálj…
+ Reagálás…
chat item menu
@@ -6569,7 +6606,7 @@ swipe action
Reject (sender NOT notified)
- Elutasítás (a kérés küldője NEM fog értesítést kapni)
+ Elutasítás (a kérés küldője NEM lesz értesítve)
No comment provided by engineer.
@@ -6595,7 +6632,11 @@ swipe action
Remove
Eltávolítás
- No comment provided by engineer.
+ alert action
+
+
+ Remove and delete messages
+ alert action
Remove archive?
@@ -6620,7 +6661,7 @@ swipe action
Remove member?
Eltávolítja a tagot?
- No comment provided by engineer.
+ alert title
Remove passphrase from keychain?
@@ -6739,7 +6780,7 @@ swipe action
Reset all statistics
- Az összes statisztika visszaállítása
+ Összes statisztika visszaállítása
No comment provided by engineer.
@@ -7038,11 +7079,31 @@ chat item action
A keresősáv elfogadja a meghívási hivatkozásokat.
No comment provided by engineer.
+
+ Search files
+ No comment provided by engineer.
+
+
+ Search images
+ No comment provided by engineer.
+
+
+ Search links
+ No comment provided by engineer.
+
Search or paste SimpleX link
Keresés vagy SimpleX-hivatkozás beillesztése
No comment provided by engineer.
+
+ Search videos
+ No comment provided by engineer.
+
+
+ Search voice messages
+ No comment provided by engineer.
+
Secondary
Másodlagos szín
@@ -7060,7 +7121,7 @@ chat item action
Security assessment
- Biztonsági kiértékelés
+ Biztonsági felmérés
No comment provided by engineer.
@@ -7070,22 +7131,22 @@ chat item action
Select
- Kijelölés
+ Kiválasztás
chat item action
Select chat profile
- Csevegési profil kijelölése
+ Csevegési profil kiválasztása
No comment provided by engineer.
Selected %lld
- %lld kijelölve
+ %lld kiválasztva
No comment provided by engineer.
Selected chat preferences prohibit this message.
- A kijelölt csevegési beállítások tiltják ezt az üzenetet.
+ A kiválasztott csevegési beállítások tiltják ezt az üzenetet.
No comment provided by engineer.
@@ -7240,7 +7301,7 @@ chat item action
Sending receipts is disabled for %lld contacts
- A kézbesítési jelentések le vannak tiltva %lld partnernél
+ A kézbesítési jelentések le vannak tiltva %lld partner számára
No comment provided by engineer.
@@ -7250,7 +7311,7 @@ chat item action
Sending receipts is enabled for %lld contacts
- A kézbesítési jelentések engedélyezve vannak %lld partnernél
+ A kézbesítési jelentések engedélyezve vannak %lld partner számára
No comment provided by engineer.
@@ -7395,7 +7456,7 @@ chat item action
Session code
- Munkamenet kód
+ Munkamenet kódja
No comment provided by engineer.
@@ -7455,7 +7516,7 @@ chat item action
Set profile bio and welcome message.
- Névjegy és üdvözlőüzenet beállítása a profilokhoz.
+ Életrajz és üdvözlőüzenet beállítása a profilokhoz.
No comment provided by engineer.
@@ -7681,7 +7742,7 @@ chat item action
SimpleX address settings
- Beállítások automatikus elfogadása
+ SimpleX-címbeállítások
alert title
@@ -7756,7 +7817,7 @@ chat item action
Small groups (max 20)
- Kis csoportok (max. 20 tag)
+ Kis csoportok (legfeljebb 20 tag)
No comment provided by engineer.
@@ -7809,7 +7870,7 @@ report reason
Start chat
- Csevegés indítása
+ Csevegés elindítása
No comment provided by engineer.
@@ -8206,12 +8267,12 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő.
The second tick we missed! ✅
- A második jelölés, amit kihagytunk! ✅
+ A második pipa, ami már nagyon hiányzott! ✅
No comment provided by engineer.
The sender will NOT be notified
- A kérés küldője NEM fog értesítést kapni
+ A kérés küldője NEM lesz értesítve
alert message
@@ -8261,12 +8322,12 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő.
This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes.
- Ez a művelet nem vonható vissza – a kijelöltnél korábban küldött és fogadott üzenetek törölve lesznek. Ez több percet is igénybe vehet.
+ Ez a művelet nem vonható vissza – a kiválasztott üzenettől korábban küldött és fogadott üzenetek törölve lesznek. Ez több percet is igénybe vehet.
No comment provided by engineer.
This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted.
- Ez a művelet nem vonható vissza – a kijelölt üzenettől korábban küldött és fogadott üzenetek törölve lesznek a csevegésből.
+ Ez a művelet nem vonható vissza – a kiválasztott üzenettől korábban küldött és fogadott üzenetek törölve lesznek a csevegésből.
alert message
@@ -8423,7 +8484,7 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll
To support instant push notifications the chat database has to be migrated.
- Az azonnali push-értesítések támogatásához a csevegési adatbázis átköltöztetése szükséges.
+ Az azonnali leküldéses értesítések támogatásához a csevegési adatbázis átköltöztetése szükséges.
No comment provided by engineer.
@@ -8438,7 +8499,7 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll
To verify end-to-end encryption with your contact compare (or scan) the code on your devices.
- A végpontok közötti titkosítás hitelesítéséhez hasonlítsa össze (vagy olvassa be a QR-kódot) a partnere eszközén lévő kóddal.
+ A végpontok közötti titkosítás ellenőrzéséhez hasonlítsa össze (vagy olvassa be a QR-kódot) a partnere eszközén lévő kóddal.
No comment provided by engineer.
@@ -8640,7 +8701,7 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso
Update database passphrase
- Az adatbázis jelmondatának módosítása
+ Adatbázis jelmondatának módosítása
No comment provided by engineer.
@@ -8845,7 +8906,7 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso
User selection
- Felhasználó kijelölése
+ Felhasználó kiválasztása
No comment provided by engineer.
@@ -8860,37 +8921,37 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso
Verify code with desktop
- Kód hitelesítése a számítógépen
+ Kód ellenőrzése a számítógépen
No comment provided by engineer.
Verify connection
- Kapcsolat hitelesítése
+ Kapcsolat ellenőrzése
No comment provided by engineer.
Verify connection security
- Biztonságos kapcsolat hitelesítése
+ Biztonságos kapcsolat ellenőrzése
No comment provided by engineer.
Verify connections
- Kapcsolatok hitelesítése
+ Kapcsolatok ellenőrzése
No comment provided by engineer.
Verify database passphrase
- Az adatbázis jelmondatának hitelesítése
+ Adatbázis jelmondatának ellenőrzése
No comment provided by engineer.
Verify passphrase
- Jelmondat hitelesítése
+ Jelmondat ellenőrzése
No comment provided by engineer.
Verify security code
- Biztonsági kód hitelesítése
+ Biztonsági kód ellenőrzése
No comment provided by engineer.
@@ -8918,6 +8979,10 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso
A videó akkor érkezik meg, amikor a küldője elérhető lesz, várjon, vagy ellenőrizze később!
No comment provided by engineer.
+
+ Videos
+ No comment provided by engineer.
+
Videos and files up to 1gb
Videók és fájlok legfeljebb 1GB méretig
@@ -9202,7 +9267,7 @@ Megismétli a csatlakozási kérést?
You are not connected to the server used to receive messages from this connection (no subscription).
- Ön nem kapcsolódott ahhoz a kiszolgálóhoz, amely az adott partnerétől érkező üzenetek fogadására szolgál (nincs előfizetés).
+ Ön nem kapcsolódott ahhoz a kiszolgálóhoz, amely az adott partnerétől érkező üzenetek fogadására szolgál (nincs feliratkozás).
subscription status explanation
@@ -9287,7 +9352,7 @@ Megismétli a csatlakozási kérést?
You can start chat via app Settings / Database or by restarting the app
- A csevegést az alkalmazás „Beállítások / Adatbázis” menüben vagy az alkalmazás újraindításával indíthatja el
+ A csevegés elindítható az alkalmazás „Beállítások / Adatbázis” menüjében vagy az alkalmazás újraindításával
No comment provided by engineer.
@@ -9322,7 +9387,7 @@ Megismétli a csatlakozási kérést?
You could not be verified; please try again.
- Nem sikerült hitelesíteni; próbálja meg újra.
+ Nem sikerült ellenőrizni; próbálja meg újra.
No comment provided by engineer.
@@ -9434,12 +9499,12 @@ Megismétli a kapcsolódási kérést?
You will stop receiving messages from this chat. Chat history will be preserved.
- Ön nem fog több üzenetet kapni ebből a csevegésből, de a csevegés előzményei megmaradnak.
+ Nem fog több üzenetet kapni ebből a csevegésből, de a csevegés előzményei megmaradnak.
No comment provided by engineer.
You will stop receiving messages from this group. Chat history will be preserved.
- Ettől a csoporttól nem fog értesítéseket kapni. A csevegési előzmények megmaradnak.
+ Nem fog több üzenetet kapni ebből a csoportból, de a csevegés előzményei megmaradnak.
No comment provided by engineer.
@@ -9514,7 +9579,7 @@ Megismétli a kapcsolódási kérést?
Your contact sent a file that is larger than currently supported maximum size (%@).
- A partnere a jelenleg megengedett maximális méretű (%@) fájlnál nagyobbat küldött.
+ A partnere a jelenleg támogatott legnagyobb (%@) fájlméretnél nagyobbat küldött.
No comment provided by engineer.
@@ -9524,7 +9589,7 @@ Megismétli a kapcsolódási kérést?
Your contacts will remain connected.
- A partnerei továbbra is kapcsolódva maradnak.
+ A partnereivel továbbra is kapcsolatban marad.
No comment provided by engineer.
@@ -9704,7 +9769,7 @@ Megismétli a kapcsolódási kérést?
audio call (not e2e encrypted)
- hanghívás (nem e2e titkosított)
+ hanghívás (végpontok között NEM titkosított)
No comment provided by engineer.
@@ -9845,7 +9910,7 @@ marked deleted chat item preview text
connecting call…
- kapcsolódási hívás…
+ hívás kapcsolása…
call status
@@ -9880,12 +9945,12 @@ marked deleted chat item preview text
contact has e2e encryption
- a partner e2e titkosítással rendelkezik
+ a partner végpontok közötti titkosítással rendelkezik
No comment provided by engineer.
contact has no e2e encryption
- a partner nem rendelkezik e2e titkosítással
+ a partner nem rendelkezik végpontok közötti titkosítással
No comment provided by engineer.
@@ -9981,7 +10046,7 @@ pref value
e2e encrypted
- e2e titkosított
+ végpontok között titkosított
No comment provided by engineer.
@@ -10041,12 +10106,12 @@ pref value
ended
- befejeződött
+ hívás vége
No comment provided by engineer.
ended call %@
- %@ hívása befejeződött
+ %@ hívása véget ért
call status
@@ -10091,12 +10156,12 @@ pref value
iOS Keychain is used to securely store passphrase - it allows receiving push notifications.
- Az iOS kulcstartó a jelmondat biztonságos tárolására szolgál – lehetővé teszi a push-értesítések fogadását.
+ Az iOS kulcstartó a jelmondat biztonságos tárolására szolgál – lehetővé teszi a leküldéses értesítések fogadását.
No comment provided by engineer.
iOS Keychain will be used to securely store passphrase after you restart the app or change passphrase - it will allow receiving push notifications.
- Az iOS kulcstartó biztonságosan fogja tárolni a jelmondatot az alkalmazás újraindítása, vagy a jelmondat módosítása után – lehetővé teszi a push-értesítések fogadását.
+ Az iOS kulcstartó biztonságosan fogja tárolni a jelmondatot az alkalmazás újraindítása, vagy a jelmondat módosítása után – lehetővé teszi a leküldéses értesítések fogadását.
No comment provided by engineer.
@@ -10261,12 +10326,12 @@ pref value
no e2e encryption
- nincs e2e titkosítás
+ nincs végpontok közötti titkosítás
No comment provided by engineer.
no subscription
- nincs előfizetés
+ nincs feliratkozás
No comment provided by engineer.
@@ -10354,12 +10419,12 @@ time to disappear
received answer…
- válasz fogadása…
+ válasz érkezett…
No comment provided by engineer.
received confirmation…
- visszaigazolás fogadása…
+ visszaigazolás érkezett…
No comment provided by engineer.
@@ -10498,7 +10563,7 @@ utoljára fogadott üzenet: %2$@
starting…
- indítás…
+ hívás indítása…
No comment provided by engineer.
@@ -10583,7 +10648,7 @@ utoljára fogadott üzenet: %2$@
video call (not e2e encrypted)
- videóhívás (nem e2e titkosított)
+ videóhívás (végpontok között NEM titkosított)
No comment provided by engineer.
@@ -10838,12 +10903,12 @@ utoljára fogadott üzenet: %2$@
Currently maximum supported file size is %@.
- Jelenleg támogatott legnagyobb fájl méret: %@.
+ Jelenleg támogatott legnagyobb fájlméret: %@.
No comment provided by engineer.
@@ -10948,7 +11013,7 @@ utoljára fogadott üzenet: %2$@
Selected chat preferences prohibit this message.
- A kijelölt csevegési beállítások tiltják ezt az üzenetet.
+ A kiválasztott csevegési beállítások tiltják ezt az üzenetet.
No comment provided by engineer.
diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff
index 5f057cd8bb..e2c826f334 100644
--- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff
+++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff
@@ -792,6 +792,10 @@ swipe action
Tutti i membri del gruppo resteranno connessi.
No comment provided by engineer.
+
+ All messages
+ No comment provided by engineer.
+
All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.
Tutti i messaggi e i file vengono inviati **crittografati end-to-end**, con sicurezza resistenti alla quantistica nei messaggi diretti.
@@ -1142,6 +1146,10 @@ swipe action
Chiamate audio e video
No comment provided by engineer.
+
+ Audio call
+ No comment provided by engineer.
+
Audio/video calls
Chiamate audio/video
@@ -2579,6 +2587,14 @@ swipe action
Eliminare il messaggio del membro?
No comment provided by engineer.
+
+ Delete member messages
+ No comment provided by engineer.
+
+
+ Delete member messages?
+ alert title
+
Delete message?
Eliminare il messaggio?
@@ -2587,7 +2603,8 @@ swipe action
Delete messages
Elimina messaggi
- alert button
+ alert action
+alert button
Delete messages after
@@ -3851,6 +3868,10 @@ snd error text
File e contenuti multimediali vietati!
No comment provided by engineer.
+
+ Filter
+ No comment provided by engineer.
+
Filter unread and favorite chats.
Filtra le chat non lette e preferite.
@@ -4335,6 +4356,10 @@ Errore: %2$@
L'immagine verrà ricevuta quando il tuo contatto sarà in linea, aspetta o controlla più tardi!
No comment provided by engineer.
+
+ Images
+ No comment provided by engineer.
+
Immediately
Immediatamente
@@ -4594,6 +4619,10 @@ Altri miglioramenti sono in arrivo!
Invita amici
No comment provided by engineer.
+
+ Invite member
+ No comment provided by engineer.
+
Invite members
Invita membri
@@ -4817,6 +4846,10 @@ Questo è il tuo link per il gruppo %@!
Desktop collegati
No comment provided by engineer.
+
+ Links
+ No comment provided by engineer.
+
List
Elenco
@@ -4942,6 +4975,10 @@ Questo è il tuo link per il gruppo %@!
Il membro è eliminato - impossibile accettare la richiesta
No comment provided by engineer.
+
+ Member messages will be deleted - this cannot be undone!
+ alert message
+
Member reports
Segnalazioni dei membri
@@ -4965,12 +5002,12 @@ Questo è il tuo link per il gruppo %@!
Member will be removed from chat - this cannot be undone!
Il membro verrà rimosso dalla chat, non è reversibile!
- No comment provided by engineer.
+ alert message
Member will be removed from group - this cannot be undone!
Il membro verrà rimosso dal gruppo, non è reversibile!
- No comment provided by engineer.
+ alert message
Member will join the group, accept member?
@@ -5854,7 +5891,7 @@ Richiede l'attivazione della VPN.
Open new group
- Apri un gruppo nuovo
+ Apri il nuovo gruppo
new chat action
@@ -6595,7 +6632,11 @@ swipe action
Remove
Rimuovi
- No comment provided by engineer.
+ alert action
+
+
+ Remove and delete messages
+ alert action
Remove archive?
@@ -6620,7 +6661,7 @@ swipe action
Remove member?
Rimuovere il membro?
- No comment provided by engineer.
+ alert title
Remove passphrase from keychain?
@@ -7038,11 +7079,31 @@ chat item action
La barra di ricerca accetta i link di invito.
No comment provided by engineer.
+
+ Search files
+ No comment provided by engineer.
+
+
+ Search images
+ No comment provided by engineer.
+
+
+ Search links
+ No comment provided by engineer.
+
Search or paste SimpleX link
Cerca o incolla un link SimpleX
No comment provided by engineer.
+
+ Search videos
+ No comment provided by engineer.
+
+
+ Search voice messages
+ No comment provided by engineer.
+
Secondary
Secondario
@@ -8918,6 +8979,10 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
Il video verrà ricevuto quando il tuo contatto sarà in linea, attendi o controlla più tardi!
No comment provided by engineer.
+
+ Videos
+ No comment provided by engineer.
+
Videos and files up to 1gb
Video e file fino a 1 GB
diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff
index 9a42ab3f7e..efd47aa52d 100644
--- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff
+++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff
@@ -625,6 +625,7 @@ swipe action
Active connections
+ アクティブな接続
No comment provided by engineer.
@@ -634,10 +635,12 @@ swipe action
Add friends
+ 友達を追加
No comment provided by engineer.
Add list
+ リストを追加
No comment provided by engineer.
@@ -661,6 +664,7 @@ swipe action
Add team members
+ チームメンバーを追加
No comment provided by engineer.
@@ -670,6 +674,7 @@ swipe action
Add to list
+ リストに追加
No comment provided by engineer.
@@ -719,6 +724,7 @@ swipe action
Address settings
+ アドレス設定
No comment provided by engineer.
@@ -742,6 +748,7 @@ swipe action
All
+ すべて
No comment provided by engineer.
@@ -772,12 +779,17 @@ swipe action
グループ全員の接続が継続します。
No comment provided by engineer.
+
+ All messages
+ No comment provided by engineer.
+
All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.
No comment provided by engineer.
All messages will be deleted - this cannot be undone!
+ すべてのメッセージが削除されます。この操作は元に戻せません!
No comment provided by engineer.
@@ -829,6 +841,7 @@ swipe action
Allow calls?
+ 通話を許可しますか?
No comment provided by engineer.
@@ -838,6 +851,7 @@ swipe action
Allow downgrade
+ ダウングレードを許可する
No comment provided by engineer.
@@ -969,6 +983,7 @@ swipe action
Another reason
+ 他の理由
report reason
@@ -1046,6 +1061,7 @@ swipe action
Archive
+ アーカイブ
No comment provided by engineer.
@@ -1079,6 +1095,7 @@ swipe action
Archived contacts
+ アーカイブされた連絡先
No comment provided by engineer.
@@ -1100,6 +1117,10 @@ swipe action
音声通話とビデオ通話
No comment provided by engineer.
+
+ Audio call
+ No comment provided by engineer.
+
Audio/video calls
音声/ビデオ通話
@@ -1350,6 +1371,7 @@ swipe action
Can't change profile
+ プロフィールを変更できません
alert title
@@ -1375,6 +1397,7 @@ new chat action
Cancel migration
+ 移行を中止する
No comment provided by engineer.
@@ -1384,6 +1407,7 @@ new chat action
Cannot forward message
+ メッセージを転送できません
No comment provided by engineer.
@@ -1460,6 +1484,7 @@ set passcode view
Chat
+ チャット
No comment provided by engineer.
@@ -1514,6 +1539,7 @@ set passcode view
Chat list
+ チャット一覧
No comment provided by engineer.
@@ -1570,6 +1596,7 @@ set passcode view
Check messages every 20 min.
+ 20分おきにメッセージを確認する。
No comment provided by engineer.
@@ -2407,6 +2434,14 @@ swipe action
メンバーのメッセージを削除しますか?
No comment provided by engineer.
+
+ Delete member messages
+ No comment provided by engineer.
+
+
+ Delete member messages?
+ alert title
+
Delete message?
メッセージを削除しますか?
@@ -2415,7 +2450,8 @@ swipe action
Delete messages
メッセージを削除
- alert button
+ alert action
+alert button
Delete messages after
@@ -3565,6 +3601,10 @@ snd error text
ファイルとメディアは禁止されています!
No comment provided by engineer.
+
+ Filter
+ No comment provided by engineer.
+
Filter unread and favorite chats.
未読とお気に入りをフィルターします。
@@ -4006,6 +4046,10 @@ Error: %2$@
連絡先がオンラインになったら受信されます。しばらくお待ちください!
No comment provided by engineer.
+
+ Images
+ No comment provided by engineer.
+
Immediately
即座に
@@ -4241,6 +4285,10 @@ More improvements are coming soon!
友人を招待する
No comment provided by engineer.
+
+ Invite member
+ No comment provided by engineer.
+
Invite members
メンバーを招待する
@@ -4448,6 +4496,10 @@ This is your link for group %@!
Linked desktops
No comment provided by engineer.
+
+ Links
+ No comment provided by engineer.
+
List
swipe action
@@ -4563,6 +4615,10 @@ This is your link for group %@!
Member is deleted - can't accept request
No comment provided by engineer.
+
+ Member messages will be deleted - this cannot be undone!
+ alert message
+
Member reports
chat feature
@@ -4583,12 +4639,12 @@ This is your link for group %@!
Member will be removed from chat - this cannot be undone!
- No comment provided by engineer.
+ alert message
Member will be removed from group - this cannot be undone!
メンバーをグループから除名する (※元に戻せません※)!
- No comment provided by engineer.
+ alert message
Member will join the group, accept member?
@@ -6045,7 +6101,11 @@ swipe action
Remove
削除
- No comment provided by engineer.
+ alert action
+
+
+ Remove and delete messages
+ alert action
Remove archive?
@@ -6067,7 +6127,7 @@ swipe action
Remove member?
メンバーを除名しますか?
- No comment provided by engineer.
+ alert title
Remove passphrase from keychain?
@@ -6442,10 +6502,30 @@ chat item action
Search bar accepts invitation links.
No comment provided by engineer.
+
+ Search files
+ No comment provided by engineer.
+
+
+ Search images
+ No comment provided by engineer.
+
+
+ Search links
+ No comment provided by engineer.
+
Search or paste SimpleX link
No comment provided by engineer.
+
+ Search videos
+ No comment provided by engineer.
+
+
+ Search voice messages
+ No comment provided by engineer.
+
Secondary
No comment provided by engineer.
@@ -8117,6 +8197,10 @@ To connect, please ask your contact to create another connection link and check
動画は相手がオンラインになったら受信されます。しばらくお待ちください!
No comment provided by engineer.
+
+ Videos
+ No comment provided by engineer.
+
Videos and files up to 1gb
1GBまでのビデオとファイル
diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff
index 8e0cdee3ca..955607acfd 100644
--- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff
+++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff
@@ -790,6 +790,10 @@ swipe action
Alle groepsleden blijven verbonden.
No comment provided by engineer.
+
+ All messages
+ No comment provided by engineer.
+
All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.
Alle berichten en bestanden worden **end-to-end versleuteld** verzonden, met post-quantumbeveiliging in directe berichten.
@@ -1138,6 +1142,10 @@ swipe action
Audio en video oproepen
No comment provided by engineer.
+
+ Audio call
+ No comment provided by engineer.
+
Audio/video calls
Audio/video oproepen
@@ -2565,6 +2573,14 @@ swipe action
Bericht van lid verwijderen?
No comment provided by engineer.
+
+ Delete member messages
+ No comment provided by engineer.
+
+
+ Delete member messages?
+ alert title
+
Delete message?
Verwijder bericht?
@@ -2573,7 +2589,8 @@ swipe action
Delete messages
Verwijder berichten
- alert button
+ alert action
+alert button
Delete messages after
@@ -3825,6 +3842,10 @@ snd error text
Bestanden en media niet toegestaan!
No comment provided by engineer.
+
+ Filter
+ No comment provided by engineer.
+
Filter unread and favorite chats.
Filter ongelezen en favoriete chats.
@@ -4305,6 +4326,10 @@ Fout: %2$@
De afbeelding wordt ontvangen wanneer uw contact online is, even geduld a.u.b. of kijk later!
No comment provided by engineer.
+
+ Images
+ No comment provided by engineer.
+
Immediately
Onmiddellijk
@@ -4564,6 +4589,10 @@ Binnenkort meer verbeteringen!
Nodig vrienden uit
No comment provided by engineer.
+
+ Invite member
+ No comment provided by engineer.
+
Invite members
Nodig leden uit
@@ -4785,6 +4814,10 @@ Dit is jouw link voor groep %@!
Gelinkte desktops
No comment provided by engineer.
+
+ Links
+ No comment provided by engineer.
+
List
Lijst
@@ -4907,6 +4940,10 @@ Dit is jouw link voor groep %@!
Member is deleted - can't accept request
No comment provided by engineer.
+
+ Member messages will be deleted - this cannot be undone!
+ alert message
+
Member reports
Ledenrapporten
@@ -4930,12 +4967,12 @@ Dit is jouw link voor groep %@!
Member will be removed from chat - this cannot be undone!
Lid wordt verwijderd uit de chat - dit kan niet ongedaan worden gemaakt!
- No comment provided by engineer.
+ alert message
Member will be removed from group - this cannot be undone!
Lid wordt uit de groep verwijderd, dit kan niet ongedaan worden gemaakt!
- No comment provided by engineer.
+ alert message
Member will join the group, accept member?
@@ -6544,7 +6581,11 @@ swipe action
Remove
Verwijderen
- No comment provided by engineer.
+ alert action
+
+
+ Remove and delete messages
+ alert action
Remove archive?
@@ -6568,7 +6609,7 @@ swipe action
Remove member?
Lid verwijderen?
- No comment provided by engineer.
+ alert title
Remove passphrase from keychain?
@@ -6982,11 +7023,31 @@ chat item action
Zoekbalk accepteert uitnodigingslinks.
No comment provided by engineer.
+
+ Search files
+ No comment provided by engineer.
+
+
+ Search images
+ No comment provided by engineer.
+
+
+ Search links
+ No comment provided by engineer.
+
Search or paste SimpleX link
Zoeken of plak een SimpleX link
No comment provided by engineer.
+
+ Search videos
+ No comment provided by engineer.
+
+
+ Search voice messages
+ No comment provided by engineer.
+
Secondary
Secundair
@@ -8832,6 +8893,10 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak
De video wordt ontvangen wanneer uw contact online is, even geduld a.u.b. of kijk later!
No comment provided by engineer.
+
+ Videos
+ No comment provided by engineer.
+
Videos and files up to 1gb
Video's en bestanden tot 1 GB
diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff
index fb46bcd1d9..f74e1e31b5 100644
--- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff
+++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff
@@ -568,10 +568,12 @@ swipe action
Accept as member
+ Zaakceptuj jako członka
alert action
Accept as observer
+ Zaakceptuj jako obserwatora
alert action
@@ -586,6 +588,7 @@ swipe action
Accept contact request
+ Zaakceptuj prośby o kontakt
alert title
@@ -601,6 +604,7 @@ swipe action
Accept member
+ Zaakceptuj członka
alert title
@@ -645,6 +649,7 @@ swipe action
Add message
+ Dodaj wiadomość
placeholder for sending contact request
@@ -787,6 +792,10 @@ swipe action
Wszyscy członkowie grupy pozostaną połączeni.
No comment provided by engineer.
+
+ All messages
+ No comment provided by engineer.
+
All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.
Wszystkie wiadomości i pliki są wysyłane **z szyfrowaniem end-to-end**, z bezpieczeństwem postkwantowym w wiadomościach bezpośrednich.
@@ -819,6 +828,7 @@ swipe action
All servers
+ Wszystkie serwery
No comment provided by engineer.
@@ -863,6 +873,7 @@ swipe action
Allow files and media only if your contact allows them.
+ Zezwalaj na pliki i media tylko wtedy, gdy Twój kontakt na to pozwala.
No comment provided by engineer.
@@ -952,6 +963,7 @@ swipe action
Allow your contacts to send files and media.
+ Pozwól kontaktom wysyłać pliki i media.
No comment provided by engineer.
@@ -1134,6 +1146,10 @@ swipe action
Połączenia audio i wideo
No comment provided by engineer.
+
+ Audio call
+ No comment provided by engineer.
+
Audio/video calls
Połączenia audio/wideo
@@ -1216,6 +1232,7 @@ swipe action
Better groups performance
+ Lepsze działanie grup
No comment provided by engineer.
@@ -1240,6 +1257,7 @@ swipe action
Better privacy and security
+ Lepsza prywatność i bezpieczeństwo
No comment provided by engineer.
@@ -1312,6 +1330,7 @@ swipe action
Bot
+ Bot
No comment provided by engineer.
@@ -1336,6 +1355,7 @@ swipe action
Both you and your contact can send files and media.
+ Zarówno Ty, jak i Twój kontakt możecie wysyłać pliki i media.
No comment provided by engineer.
@@ -2528,6 +2548,14 @@ swipe action
Usunąć wiadomość członka?
No comment provided by engineer.
+
+ Delete member messages
+ No comment provided by engineer.
+
+
+ Delete member messages?
+ alert title
+
Delete message?
Usunąć wiadomość?
@@ -2536,7 +2564,8 @@ swipe action
Delete messages
Usuń wiadomości
- alert button
+ alert action
+alert button
Delete messages after
@@ -3755,6 +3784,10 @@ snd error text
Pliki i media zabronione!
No comment provided by engineer.
+
+ Filter
+ No comment provided by engineer.
+
Filter unread and favorite chats.
Filtruj nieprzeczytane i ulubione czaty.
@@ -4222,6 +4255,10 @@ Błąd: %2$@
Obraz zostanie odebrany, gdy kontakt będzie online, poczekaj lub sprawdź później!
No comment provided by engineer.
+
+ Images
+ No comment provided by engineer.
+
Immediately
Natychmiast
@@ -4472,6 +4509,10 @@ More improvements are coming soon!
Zaproś znajomych
No comment provided by engineer.
+
+ Invite member
+ No comment provided by engineer.
+
Invite members
Zaproś członków
@@ -4690,6 +4731,10 @@ To jest twój link do grupy %@!
Połączone komputery
No comment provided by engineer.
+
+ Links
+ No comment provided by engineer.
+
List
swipe action
@@ -4808,6 +4853,10 @@ To jest twój link do grupy %@!
Member is deleted - can't accept request
No comment provided by engineer.
+
+ Member messages will be deleted - this cannot be undone!
+ alert message
+
Member reports
chat feature
@@ -4828,12 +4877,12 @@ To jest twój link do grupy %@!
Member will be removed from chat - this cannot be undone!
- No comment provided by engineer.
+ alert message
Member will be removed from group - this cannot be undone!
Członek zostanie usunięty z grupy - nie można tego cofnąć!
- No comment provided by engineer.
+ alert message
Member will join the group, accept member?
@@ -6387,7 +6436,11 @@ swipe action
Remove
Usuń
- No comment provided by engineer.
+ alert action
+
+
+ Remove and delete messages
+ alert action
Remove archive?
@@ -6411,7 +6464,7 @@ swipe action
Remove member?
Usunąć członka?
- No comment provided by engineer.
+ alert title
Remove passphrase from keychain?
@@ -6809,11 +6862,31 @@ chat item action
Pasek wyszukiwania akceptuje linki zaproszenia.
No comment provided by engineer.
+
+ Search files
+ No comment provided by engineer.
+
+
+ Search images
+ No comment provided by engineer.
+
+
+ Search links
+ No comment provided by engineer.
+
Search or paste SimpleX link
Wyszukaj lub wklej link SimpleX
No comment provided by engineer.
+
+ Search videos
+ No comment provided by engineer.
+
+
+ Search voice messages
+ No comment provided by engineer.
+
Secondary
Drugorzędny
@@ -8609,6 +8682,10 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc
Film zostanie odebrany, gdy kontakt będzie online, poczekaj lub sprawdź później!
No comment provided by engineer.
+
+ Videos
+ No comment provided by engineer.
+
Videos and files up to 1gb
Filmy i pliki do 1gb
diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff
index d885db1350..64905cf68c 100644
--- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff
+++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff
@@ -167,7 +167,7 @@
%d hours
- %d час.
+ %d ч.
time interval
@@ -792,6 +792,10 @@ swipe action
Все члены группы останутся соединены.
No comment provided by engineer.
+
+ All messages
+ No comment provided by engineer.
+
All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.
Все сообщения и файлы отправляются с **end-to-end шифрованием**, с постквантовой безопасностью в прямых разговорах.
@@ -1134,7 +1138,7 @@ swipe action
Audio & video calls
- Аудио- и видеозвонки
+ Аудио и видеозвонки
No comment provided by engineer.
@@ -1142,6 +1146,10 @@ swipe action
Аудио и видео звонки
No comment provided by engineer.
+
+ Audio call
+ No comment provided by engineer.
+
Audio/video calls
Аудио/видео звонки
@@ -2579,6 +2587,14 @@ swipe action
Удалить сообщение участника?
No comment provided by engineer.
+
+ Delete member messages
+ No comment provided by engineer.
+
+
+ Delete member messages?
+ alert title
+
Delete message?
Удалить сообщение?
@@ -2587,7 +2603,8 @@ swipe action
Delete messages
Удалить сообщения
- alert button
+ alert action
+alert button
Delete messages after
@@ -3312,6 +3329,7 @@ chat item action
Error connecting to the server used to receive messages from this connection: %@
+ Ошибка подключения к серверу, используемому для получения сообщений от этого соединения: %@
subscription status explanation
@@ -3850,6 +3868,10 @@ snd error text
Файлы и медиа запрещены!
No comment provided by engineer.
+
+ Filter
+ No comment provided by engineer.
+
Filter unread and favorite chats.
Фильтровать непрочитанные и избранные чаты.
@@ -4334,6 +4356,10 @@ Error: %2$@
Изображение будет принято, когда Ваш контакт будет в сети, подождите или проверьте позже!
No comment provided by engineer.
+
+ Images
+ No comment provided by engineer.
+
Immediately
Сразу
@@ -4592,6 +4618,10 @@ More improvements are coming soon!
Пригласить друзей
No comment provided by engineer.
+
+ Invite member
+ No comment provided by engineer.
+
Invite members
Пригласить членов группы
@@ -4815,6 +4845,10 @@ This is your link for group %@!
Связанные компьютеры
No comment provided by engineer.
+
+ Links
+ No comment provided by engineer.
+
List
Список
@@ -4940,6 +4974,10 @@ This is your link for group %@!
Член группы удалён - невозможно принять запрос
No comment provided by engineer.
+
+ Member messages will be deleted - this cannot be undone!
+ alert message
+
Member reports
Сообщения о нарушениях
@@ -4963,12 +5001,12 @@ This is your link for group %@!
Member will be removed from chat - this cannot be undone!
Член будет удален из разговора - это действие нельзя отменить!
- No comment provided by engineer.
+ alert message
Member will be removed from group - this cannot be undone!
Член группы будет удален - это действие нельзя отменить!
- No comment provided by engineer.
+ alert message
Member will join the group, accept member?
@@ -6593,7 +6631,11 @@ swipe action
Remove
Удалить
- No comment provided by engineer.
+ alert action
+
+
+ Remove and delete messages
+ alert action
Remove archive?
@@ -6618,7 +6660,7 @@ swipe action
Remove member?
Удалить члена группы?
- No comment provided by engineer.
+ alert title
Remove passphrase from keychain?
@@ -7036,11 +7078,31 @@ chat item action
Поле поиска поддерживает ссылки-приглашения.
No comment provided by engineer.
+
+ Search files
+ No comment provided by engineer.
+
+
+ Search images
+ No comment provided by engineer.
+
+
+ Search links
+ No comment provided by engineer.
+
Search or paste SimpleX link
Искать или вставьте ссылку SimpleX
No comment provided by engineer.
+
+ Search videos
+ No comment provided by engineer.
+
+
+ Search voice messages
+ No comment provided by engineer.
+
Secondary
Вторичный
@@ -8411,7 +8473,7 @@ You will be prompted to complete authentication before this feature is enabled.<
To send
- Для оправки
+ Для отправки
No comment provided by engineer.
@@ -8476,6 +8538,7 @@ You will be prompted to complete authentication before this feature is enabled.<
Trying to connect to the server used to receive messages from this connection.
+ Попытка подключиться к серверу, используемому для получения сообщений от этого соединения.
subscription status explanation
@@ -8915,6 +8978,10 @@ To connect, please ask your contact to create another connection link and check
Видео будет получено, когда Ваш контакт будет онлайн, пожалуйста, подождите или проверьте позже!
No comment provided by engineer.
+
+ Videos
+ No comment provided by engineer.
+
Videos and files up to 1gb
Видео и файлы до 1гб
@@ -9189,6 +9256,7 @@ Repeat join request?
You are connected to the server used to receive messages from this connection.
+ Вы подключены к серверу, используемому для приема сообщений от этого соединения.
subscription status explanation
@@ -9198,6 +9266,7 @@ Repeat join request?
You are not connected to the server used to receive messages from this connection (no subscription).
+ Вы не подключены к серверу, используемому для получения сообщений по этому соединению (нет подписки).
subscription status explanation
@@ -10261,6 +10330,7 @@ pref value
no subscription
+ нет подписки
No comment provided by engineer.
diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff
index ecb4d20fbb..4ff953c62e 100644
--- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff
+++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff
@@ -718,6 +718,10 @@ swipe action
สมาชิกในกลุ่มทุกคนจะยังคงเชื่อมต่ออยู่.
No comment provided by engineer.
+
+ All messages
+ No comment provided by engineer.
+
All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.
No comment provided by engineer.
@@ -1034,6 +1038,10 @@ swipe action
การโทรด้วยเสียงและวิดีโอ
No comment provided by engineer.
+
+ Audio call
+ No comment provided by engineer.
+
Audio/video calls
การโทรด้วยเสียง/วิดีโอ
@@ -2317,6 +2325,14 @@ swipe action
ลบข้อความสมาชิก?
No comment provided by engineer.
+
+ Delete member messages
+ No comment provided by engineer.
+
+
+ Delete member messages?
+ alert title
+
Delete message?
ลบข้อความ?
@@ -2325,7 +2341,8 @@ swipe action
Delete messages
ลบข้อความ
- alert button
+ alert action
+alert button
Delete messages after
@@ -3468,6 +3485,10 @@ snd error text
ไฟล์และสื่อต้องห้าม!
No comment provided by engineer.
+
+ Filter
+ No comment provided by engineer.
+
Filter unread and favorite chats.
กรองแชทที่ยังไม่อ่านและแชทโปรด
@@ -3909,6 +3930,10 @@ Error: %2$@
จะได้รับรูปภาพเมื่อผู้ติดต่อของคุณออนไลน์ โปรดรอหรือตรวจสอบในภายหลัง!
No comment provided by engineer.
+
+ Images
+ No comment provided by engineer.
+
Immediately
โดยทันที
@@ -4142,6 +4167,10 @@ More improvements are coming soon!
เชิญเพื่อนๆ
No comment provided by engineer.
+
+ Invite member
+ No comment provided by engineer.
+
Invite members
เชิญสมาชิก
@@ -4349,6 +4378,10 @@ This is your link for group %@!
Linked desktops
No comment provided by engineer.
+
+ Links
+ No comment provided by engineer.
+
List
swipe action
@@ -4464,6 +4497,10 @@ This is your link for group %@!
Member is deleted - can't accept request
No comment provided by engineer.
+
+ Member messages will be deleted - this cannot be undone!
+ alert message
+
Member reports
chat feature
@@ -4484,12 +4521,12 @@ This is your link for group %@!
Member will be removed from chat - this cannot be undone!
- No comment provided by engineer.
+ alert message
Member will be removed from group - this cannot be undone!
สมาชิกจะถูกลบออกจากกลุ่ม - ไม่สามารถยกเลิกได้!
- No comment provided by engineer.
+ alert message
Member will join the group, accept member?
@@ -5936,7 +5973,11 @@ swipe action
Remove
ลบ
- No comment provided by engineer.
+ alert action
+
+
+ Remove and delete messages
+ alert action
Remove archive?
@@ -5958,7 +5999,7 @@ swipe action
Remove member?
ลบสมาชิกออก?
- No comment provided by engineer.
+ alert title
Remove passphrase from keychain?
@@ -6333,10 +6374,30 @@ chat item action
Search bar accepts invitation links.
No comment provided by engineer.
+
+ Search files
+ No comment provided by engineer.
+
+
+ Search images
+ No comment provided by engineer.
+
+
+ Search links
+ No comment provided by engineer.
+
Search or paste SimpleX link
No comment provided by engineer.
+
+ Search videos
+ No comment provided by engineer.
+
+
+ Search voice messages
+ No comment provided by engineer.
+
Secondary
No comment provided by engineer.
@@ -8008,6 +8069,10 @@ To connect, please ask your contact to create another connection link and check
จะได้รับวิดีโอเมื่อผู้ติดต่อของคุณออนไลน์ โปรดรอหรือตรวจสอบในภายหลัง!
No comment provided by engineer.
+
+ Videos
+ No comment provided by engineer.
+
Videos and files up to 1gb
วิดีโอและไฟล์สูงสุด 1gb
diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff
index 57151a95b5..346d9a2bdc 100644
--- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff
+++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff
@@ -792,6 +792,10 @@ swipe action
Tüm grup üyeleri bağlı kalacaktır.
No comment provided by engineer.
+
+ All messages
+ No comment provided by engineer.
+
All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.
Bütün mesajlar ve dosyalar **uçtan-uca şifrelemeli** gönderilir, doğrudan mesajlarda kuantum güvenlik ile birlikte.
@@ -1142,6 +1146,10 @@ swipe action
Sesli ve görüntülü aramalar
No comment provided by engineer.
+
+ Audio call
+ No comment provided by engineer.
+
Audio/video calls
Sesli/görüntülü aramalar
@@ -2579,6 +2587,14 @@ swipe action
Kişinin mesajı silinsin mi?
No comment provided by engineer.
+
+ Delete member messages
+ No comment provided by engineer.
+
+
+ Delete member messages?
+ alert title
+
Delete message?
Mesaj silinsin mi?
@@ -2587,7 +2603,8 @@ swipe action
Delete messages
Mesajları sil
- alert button
+ alert action
+alert button
Delete messages after
@@ -3849,6 +3866,10 @@ snd error text
Dosyalar ve medya yasaklandı!
No comment provided by engineer.
+
+ Filter
+ No comment provided by engineer.
+
Filter unread and favorite chats.
Favori ve okunmamış sohbetleri filtrele.
@@ -4330,6 +4351,10 @@ Hata: %2$@
Kişi çevrimiçi olduğunda fotoğraf alınacaktır, lütfen bekleyin veya daha sonra kontrol et!
No comment provided by engineer.
+
+ Images
+ No comment provided by engineer.
+
Immediately
Hemen
@@ -4589,6 +4614,10 @@ Daha fazla iyileştirme yakında geliyor!
Arkadaşları davet et
No comment provided by engineer.
+
+ Invite member
+ No comment provided by engineer.
+
Invite members
Üyeleri davet et
@@ -4812,6 +4841,10 @@ Bu senin grup için bağlantın %@!
Bağlanmış bilgisayarlar
No comment provided by engineer.
+
+ Links
+ No comment provided by engineer.
+
List
Liste
@@ -4937,6 +4970,10 @@ Bu senin grup için bağlantın %@!
Üye silinmiş - istek kabul edilemez
No comment provided by engineer.
+
+ Member messages will be deleted - this cannot be undone!
+ alert message
+
Member reports
Üye raporları
@@ -4960,12 +4997,12 @@ Bu senin grup için bağlantın %@!
Member will be removed from chat - this cannot be undone!
Üye sohbetten kaldırılacak - bu geri alınamaz!
- No comment provided by engineer.
+ alert message
Member will be removed from group - this cannot be undone!
Üye gruptan çıkarılacaktır - bu geri alınamaz!
- No comment provided by engineer.
+ alert message
Member will join the group, accept member?
@@ -6590,7 +6627,11 @@ swipe action
Remove
Sil
- No comment provided by engineer.
+ alert action
+
+
+ Remove and delete messages
+ alert action
Remove archive?
@@ -6615,7 +6656,7 @@ swipe action
Remove member?
Kişi silinsin mi?
- No comment provided by engineer.
+ alert title
Remove passphrase from keychain?
@@ -7033,11 +7074,31 @@ chat item action
Arama çubuğu davet bağlantılarını kabul eder.
No comment provided by engineer.
+
+ Search files
+ No comment provided by engineer.
+
+
+ Search images
+ No comment provided by engineer.
+
+
+ Search links
+ No comment provided by engineer.
+
Search or paste SimpleX link
Ara veya SimpleX bağlantısını yapıştır
No comment provided by engineer.
+
+ Search videos
+ No comment provided by engineer.
+
+
+ Search voice messages
+ No comment provided by engineer.
+
Secondary
İkincil renk
@@ -8912,6 +8973,10 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste
Kişiniz çevrimiçi olduğunda video alınacaktır, lütfen bekleyin veya daha sonra kontrol edin!
No comment provided by engineer.
+
+ Videos
+ No comment provided by engineer.
+
Videos and files up to 1gb
1gb'a kadar videolar ve dosyalar
diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff
index 7980685349..6c103e17e1 100644
--- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff
+++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff
@@ -792,6 +792,10 @@ swipe action
Всі учасники групи залишаться на зв'язку.
No comment provided by engineer.
+
+ All messages
+ No comment provided by engineer.
+
All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.
Всі повідомлення та файли надсилаються **наскрізним шифруванням**, з пост-квантовим захистом у прямих повідомленнях.
@@ -1140,6 +1144,10 @@ swipe action
Аудіо та відеодзвінки
No comment provided by engineer.
+
+ Audio call
+ No comment provided by engineer.
+
Audio/video calls
Аудіо/відео дзвінки
@@ -2574,6 +2582,14 @@ swipe action
Видалити повідомлення учасника?
No comment provided by engineer.
+
+ Delete member messages
+ No comment provided by engineer.
+
+
+ Delete member messages?
+ alert title
+
Delete message?
Видалити повідомлення?
@@ -2582,7 +2598,8 @@ swipe action
Delete messages
Видалити повідомлення
- alert button
+ alert action
+alert button
Delete messages after
@@ -3841,6 +3858,10 @@ snd error text
Файли та медіа заборонені!
No comment provided by engineer.
+
+ Filter
+ No comment provided by engineer.
+
Filter unread and favorite chats.
Фільтруйте непрочитані та улюблені чати.
@@ -4322,6 +4343,10 @@ Error: %2$@
Зображення буде отримано, коли ваш контакт буде онлайн, будь ласка, зачекайте або перевірте пізніше!
No comment provided by engineer.
+
+ Images
+ No comment provided by engineer.
+
Immediately
Негайно
@@ -4581,6 +4606,10 @@ More improvements are coming soon!
Запросити друзів
No comment provided by engineer.
+
+ Invite member
+ No comment provided by engineer.
+
Invite members
Запросити учасників
@@ -4804,6 +4833,10 @@ This is your link for group %@!
Пов'язані робочі столи
No comment provided by engineer.
+
+ Links
+ No comment provided by engineer.
+
List
Список
@@ -4927,6 +4960,10 @@ This is your link for group %@!
Member is deleted - can't accept request
No comment provided by engineer.
+
+ Member messages will be deleted - this cannot be undone!
+ alert message
+
Member reports
Повідомлення учасників
@@ -4950,12 +4987,12 @@ This is your link for group %@!
Member will be removed from chat - this cannot be undone!
Учасника буде видалено з чату – це неможливо скасувати!
- No comment provided by engineer.
+ alert message
Member will be removed from group - this cannot be undone!
Учасник буде видалений з групи - це неможливо скасувати!
- No comment provided by engineer.
+ alert message
Member will join the group, accept member?
@@ -6575,7 +6612,11 @@ swipe action
Remove
Видалити
- No comment provided by engineer.
+ alert action
+
+
+ Remove and delete messages
+ alert action
Remove archive?
@@ -6599,7 +6640,7 @@ swipe action
Remove member?
Видалити учасника?
- No comment provided by engineer.
+ alert title
Remove passphrase from keychain?
@@ -7017,11 +7058,31 @@ chat item action
Рядок пошуку приймає посилання-запрошення.
No comment provided by engineer.
+
+ Search files
+ No comment provided by engineer.
+
+
+ Search images
+ No comment provided by engineer.
+
+
+ Search links
+ No comment provided by engineer.
+
Search or paste SimpleX link
Знайдіть або вставте посилання SimpleX
No comment provided by engineer.
+
+ Search videos
+ No comment provided by engineer.
+
+
+ Search voice messages
+ No comment provided by engineer.
+
Secondary
Вторинний
@@ -8892,6 +8953,10 @@ To connect, please ask your contact to create another connection link and check
Відео буде отримано, коли ваш контакт буде онлайн, будь ласка, зачекайте або перевірте пізніше!
No comment provided by engineer.
+
+ Videos
+ No comment provided by engineer.
+
Videos and files up to 1gb
Відео та файли до 1 Гб
diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff
index e1ce65b5ce..ff7b4fa141 100644
--- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff
+++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff
@@ -568,10 +568,12 @@ swipe action
Accept as member
+ 接受为成员
alert action
Accept as observer
+ 接受为观察员
alert action
@@ -586,6 +588,7 @@ swipe action
Accept contact request
+ 接受联络请求
alert title
@@ -601,6 +604,7 @@ swipe action
Accept member
+ 接受成员
alert title
@@ -645,6 +649,7 @@ swipe action
Add message
+ 添加信息
placeholder for sending contact request
@@ -787,6 +792,10 @@ swipe action
所有群组成员将保持连接。
No comment provided by engineer.
+
+ All messages
+ No comment provided by engineer.
+
All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.
所有消息和文件均通过**端到端加密**发送;私信以量子安全方式发送。
@@ -864,6 +873,7 @@ swipe action
Allow files and media only if your contact allows them.
+ 只有你的联系人允许的情况下才允许文件和媒体。
No comment provided by engineer.
@@ -953,6 +963,7 @@ swipe action
Allow your contacts to send files and media.
+ 允许你的联系人发送文件和媒体。
No comment provided by engineer.
@@ -1135,6 +1146,10 @@ swipe action
语音和视频通话
No comment provided by engineer.
+
+ Audio call
+ No comment provided by engineer.
+
Audio/video calls
音频/视频通话
@@ -1257,10 +1272,12 @@ swipe action
Bio
+ 自我介绍
No comment provided by engineer.
Bio too large
+ 自我介绍过大
alert title
@@ -1315,6 +1332,7 @@ swipe action
Bot
+ 机器人
No comment provided by engineer.
@@ -1339,6 +1357,7 @@ swipe action
Both you and your contact can send files and media.
+ 你和你的联系人都可发送文件和媒体。
No comment provided by engineer.
@@ -1363,6 +1382,7 @@ swipe action
Business connection
+ 企业连接
No comment provided by engineer.
@@ -1416,6 +1436,7 @@ swipe action
Can't change profile
+ 无法更改个人资料
alert title
@@ -1633,14 +1654,17 @@ set passcode view
Chat with admins
+ 和管理员聊天
chat toolbar
Chat with member
+ 和成员聊天
No comment provided by engineer.
Chat with members before they join.
+ 在成员加入前和这些人聊天
No comment provided by engineer.
@@ -1650,6 +1674,7 @@ set passcode view
Chats with members
+ 和成员聊天
No comment provided by engineer.
@@ -1879,6 +1904,7 @@ set passcode view
Connect faster! 🚀
+ 更快地连接!🚀
No comment provided by engineer.
@@ -2088,6 +2114,7 @@ This is your own one-time link!
Contact requests from groups
+ 来自群的联络请求
No comment provided by engineer.
@@ -2207,6 +2234,7 @@ This is your own one-time link!
Create your address
+ 创建地址
No comment provided by engineer.
@@ -2465,6 +2493,7 @@ swipe action
Delete chat with member?
+ 删除和成员的聊天吗?
alert title
@@ -2557,6 +2586,14 @@ swipe action
删除成员消息?
No comment provided by engineer.
+
+ Delete member messages
+ No comment provided by engineer.
+
+
+ Delete member messages?
+ alert title
+
Delete message?
删除消息吗?
@@ -2565,7 +2602,8 @@ swipe action
Delete messages
删除消息
- alert button
+ alert action
+alert button
Delete messages after
@@ -2664,6 +2702,7 @@ swipe action
Deprecated options
+ 已废弃的选项
No comment provided by engineer.
@@ -2673,6 +2712,7 @@ swipe action
Description too large
+ 描述过大
alert title
@@ -2983,6 +3023,7 @@ chat item action
Empty message!
+ 空消息!
No comment provided by engineer.
@@ -3022,6 +3063,7 @@ chat item action
Enable disappearing messages by default.
+ 默认启用定时消失消息。
No comment provided by engineer.
@@ -3226,6 +3268,7 @@ chat item action
Error accepting member
+ 接受成员出错
alert title
@@ -3240,6 +3283,7 @@ chat item action
Error adding short link
+ 添加短链接出错
No comment provided by engineer.
@@ -3249,6 +3293,7 @@ chat item action
Error changing chat profile
+ 更改聊天资料出错
alert title
@@ -3273,6 +3318,7 @@ chat item action
Error checking token status
+ 查询token状态出错
No comment provided by engineer.
@@ -3331,6 +3377,7 @@ chat item action
Error deleting chat
+ 删除聊天出错
alert title
@@ -3425,6 +3472,7 @@ chat item action
Error opening group
+ 打开群时出错
No comment provided by engineer.
@@ -3449,6 +3497,7 @@ chat item action
Error rejecting contact request
+ 拒绝联络请求出错
alert title
@@ -3528,6 +3577,7 @@ chat item action
Error setting auto-accept
+ 设置自动接受出错
No comment provided by engineer.
@@ -3614,6 +3664,7 @@ snd error text
Error: %@.
+ 错误:%@。
server test error
@@ -3797,6 +3848,7 @@ snd error text
Files and media are prohibited in this chat.
+ 此聊天禁止文件和媒体。
No comment provided by engineer.
@@ -3814,6 +3866,10 @@ snd error text
禁止文件和媒体!
No comment provided by engineer.
+
+ Filter
+ No comment provided by engineer.
+
Filter unread and favorite chats.
过滤未读和收藏的聊天记录。
@@ -3841,10 +3897,12 @@ snd error text
Fingerprint in destination server address does not match certificate: %@.
+ 目的地服务器的指纹与证书不符:%@。
No comment provided by engineer.
Fingerprint in forwarding server address does not match certificate: %@.
+ 转发服务器的指纹与证书不符:%@。
No comment provided by engineer.
@@ -3854,6 +3912,7 @@ snd error text
Fingerprint in server address does not match certificate: %@.
+ 服务器的指纹与证书不符:%@。
No comment provided by engineer.
@@ -4132,6 +4191,7 @@ Error: %2$@
Group profile was changed. If you save it, the updated profile will be sent to group members.
+ 群资料已修改。如果你进行保存,修改后的群资料将发送给其他群成员。
alert message
@@ -4294,6 +4354,10 @@ Error: %2$@
图片将在您的联系人在线时收到,请稍等或稍后查看!
No comment provided by engineer.
+
+ Images
+ No comment provided by engineer.
+
Immediately
立即
@@ -4553,6 +4617,10 @@ More improvements are coming soon!
邀请朋友
No comment provided by engineer.
+
+ Invite member
+ No comment provided by engineer.
+
Invite members
邀请成员
@@ -4683,6 +4751,7 @@ This is your link for group %@!
Keep your chats clean
+ 保持聊天洁净
No comment provided by engineer.
@@ -4742,6 +4811,7 @@ This is your link for group %@!
Less traffic on mobile networks.
+ 消耗更少的移动网络数据。
No comment provided by engineer.
@@ -4774,6 +4844,10 @@ This is your link for group %@!
已链接桌面
No comment provided by engineer.
+
+ Links
+ No comment provided by engineer.
+
List
列表
@@ -4801,6 +4875,7 @@ This is your link for group %@!
Loading profile…
+ 正加载个人资料…
in progress text
@@ -4880,10 +4955,12 @@ This is your link for group %@!
Member %@
+ 成员 %@
past/unknown group member
Member admission
+ 成员准入
No comment provided by engineer.
@@ -4893,8 +4970,13 @@ This is your link for group %@!
Member is deleted - can't accept request
+ 成员被删除——无法接受请求
No comment provided by engineer.
+
+ Member messages will be deleted - this cannot be undone!
+ alert message
+
Member reports
成员举报
@@ -4918,15 +5000,16 @@ This is your link for group %@!
Member will be removed from chat - this cannot be undone!
将从聊天中删除成员 - 此操作无法撤销!
- No comment provided by engineer.
+ alert message
Member will be removed from group - this cannot be undone!
成员将被移出群组——此操作无法撤消!
- No comment provided by engineer.
+ alert message
Member will join the group, accept member?
+ 成员将加入本群,接受成员吗?
alert message
@@ -5006,6 +5089,7 @@ This is your link for group %@!
Message instantly once you tap Connect.
+ 轻按连接后即刻发消息。
No comment provided by engineer.
@@ -5085,6 +5169,7 @@ This is your link for group %@!
Messages are protected by **end-to-end encryption**.
+ 消息已通过**端到端加密**保护。
No comment provided by engineer.
@@ -5344,6 +5429,7 @@ This is your link for group %@!
New group role: Moderator
+ 新的群角色:协管
No comment provided by engineer.
@@ -5363,6 +5449,7 @@ This is your link for group %@!
New member wants to join the group.
+ 新成员要加入本群。
rcv group event chat item
@@ -5407,6 +5494,7 @@ This is your link for group %@!
No chats with members
+ 没有和成员的聊天
No comment provided by engineer.
@@ -5491,6 +5579,7 @@ This is your link for group %@!
No private routing session
+ 无私密路由会话
alert title
@@ -5700,6 +5789,7 @@ Requires compatible VPN.
Only you can send files and media.
+ 只有你可以发送文件和媒体。
No comment provided by engineer.
@@ -5729,6 +5819,7 @@ Requires compatible VPN.
Only your contact can send files and media.
+ 只有你的联系人可以发送文件和媒体。
No comment provided by engineer.
@@ -5763,6 +5854,7 @@ Requires compatible VPN.
Open clean link
+ 打开干净链接
alert action
@@ -5772,6 +5864,7 @@ Requires compatible VPN.
Open full link
+ 打开完整链接
alert action
@@ -5781,6 +5874,7 @@ Requires compatible VPN.
Open link?
+ 打开链接?
alert title
@@ -5790,26 +5884,32 @@ Requires compatible VPN.
Open new chat
+ 打开新聊天
new chat action
Open new group
+ 打开新群
new chat action
Open to accept
+ 打开以接受
No comment provided by engineer.
Open to connect
+ 打开以连接
No comment provided by engineer.
Open to join
+ 打开以加入
No comment provided by engineer.
Open to use bot
+ 打开来使用机器人
No comment provided by engineer.
@@ -5870,6 +5970,8 @@ Requires compatible VPN.
Other file errors:
%@
+ 其他文件错误:
+%@
alert message
@@ -6048,18 +6150,22 @@ Error: %@
Please try to disable and re-enable notfications.
+ 请尝试禁用并重新启用通知。
token info
Please wait for group moderators to review your request to join the group.
+ 请等待群的协管审核你加入该群的请求。
snd group event chat item
Please wait for token activation to complete.
+ 请等待token激活完成。
token info
Please wait for token to be registered.
+ 请等待token注册完成。
token info
@@ -6069,6 +6175,7 @@ Error: %@
Port
+ 端口
No comment provided by engineer.
@@ -6083,6 +6190,7 @@ Error: %@
Preset servers
+ 预设服务器
No comment provided by engineer.
@@ -6102,6 +6210,7 @@ Error: %@
Privacy for your customers.
+ 客户隐私。
No comment provided by engineer.
@@ -6126,6 +6235,7 @@ Error: %@
Private media file names.
+ 私密媒体文件名。
No comment provided by engineer.
@@ -6155,6 +6265,7 @@ Error: %@
Private routing timeout
+ 私密路由超时
alert title
@@ -6209,6 +6320,7 @@ Error: %@
Prohibit reporting messages to moderators.
+ 禁止向 协管 举报消息。
No comment provided by engineer.
@@ -6260,6 +6372,7 @@ Enable in *Network & servers* settings.
Protocol background timeout
+ 协议后台超时
No comment provided by engineer.
@@ -6284,6 +6397,7 @@ Enable in *Network & servers* settings.
Proxy requires password
+ 代理需要密码
No comment provided by engineer.
@@ -6468,6 +6582,7 @@ Enable in *Network & servers* settings.
Register
+ 注册
No comment provided by engineer.
@@ -6476,6 +6591,7 @@ Enable in *Network & servers* settings.
Registered
+ 已注册
token status text
@@ -6497,6 +6613,7 @@ swipe action
Reject member?
+ 拒绝成员?
alert title
@@ -6512,10 +6629,15 @@ swipe action
Remove
移除
- No comment provided by engineer.
+ alert action
+
+
+ Remove and delete messages
+ alert action
Remove archive?
+ 删除存档?
No comment provided by engineer.
@@ -6525,6 +6647,7 @@ swipe action
Remove link tracking
+ 删除链接跟踪
No comment provided by engineer.
@@ -6535,7 +6658,7 @@ swipe action
Remove member?
删除成员吗?
- No comment provided by engineer.
+ alert title
Remove passphrase from keychain?
@@ -6544,6 +6667,7 @@ swipe action
Removes messages and blocks members.
+ 删除消息并封禁成员。
No comment provided by engineer.
@@ -6583,46 +6707,57 @@ swipe action
Report
+ 举报
chat item action
Report content: only group moderators will see it.
+ 举报内容:仅协管会看到。
report reason
Report member profile: only group moderators will see it.
+ 举报成员个人资料:仅协管会看到。
report reason
Report other: only group moderators will see it.
+ 举报其他:仅协管会看到。
report reason
Report reason?
+ 举报理由?
No comment provided by engineer.
Report sent to moderators
+ 举报已发送至 协管
alert title
Report spam: only group moderators will see it.
+ 举报垃圾信息:仅协管会看到。
report reason
Report violation: only group moderators will see it.
+ 举报违规:仅协管会看到。
report reason
Report: %@
+ 举报: %@
report in notification
Reporting messages to moderators is prohibited.
+ 向协管举报消息已被禁止。
No comment provided by engineer.
Reports
+ 举报
No comment provided by engineer.
@@ -6717,10 +6852,12 @@ swipe action
Review group members
+ 审核群成员
No comment provided by engineer.
Review members
+ 审核成员
admission stage
@@ -6759,6 +6896,7 @@ swipe action
SOCKS proxy
+ SOCKS代理
No comment provided by engineer.
@@ -6784,10 +6922,12 @@ chat item action
Save (and notify members)
+ 保存(并通知成员)
alert button
Save admission settings?
+ 保存入群设置?
alert title
@@ -6817,6 +6957,7 @@ chat item action
Save group profile?
+ 保存群资料?
alert title
@@ -6934,11 +7075,31 @@ chat item action
搜索栏接受邀请链接。
No comment provided by engineer.
+
+ Search files
+ No comment provided by engineer.
+
+
+ Search images
+ No comment provided by engineer.
+
+
+ Search links
+ No comment provided by engineer.
+
Search or paste SimpleX link
搜索或粘贴 SimpleX 链接
No comment provided by engineer.
+
+ Search videos
+ No comment provided by engineer.
+
+
+ Search voice messages
+ No comment provided by engineer.
+
Secondary
二级
@@ -6971,6 +7132,7 @@ chat item action
Select chat profile
+ 选择聊天个人资料
No comment provided by engineer.
@@ -7015,6 +7177,7 @@ chat item action
Send contact request?
+ 发送联络请求?
No comment provided by engineer.
@@ -7069,6 +7232,7 @@ chat item action
Send private reports
+ 发送私下举报
No comment provided by engineer.
@@ -7083,10 +7247,12 @@ chat item action
Send request
+ 发送请求
No comment provided by engineer.
Send request without message
+ 发送无消息请求
No comment provided by engineer.
@@ -7101,6 +7267,7 @@ chat item action
Send your private feedback to groups.
+ 向群发送私密反馈。
No comment provided by engineer.
@@ -7200,10 +7367,12 @@ chat item action
Server
+ 服务器
No comment provided by engineer.
Server added to operator %@.
+ 服务器已添加到运营方 %@。
alert message
@@ -7223,14 +7392,17 @@ chat item action
Server operator changed.
+ 服务器运营方已更改。
alert title
Server operators
+ 服务器运营方
No comment provided by engineer.
Server protocol changed.
+ 服务器协议已更改。
alert title
@@ -7290,6 +7462,7 @@ chat item action
Set chat name…
+ 设置聊天名称…
No comment provided by engineer.
@@ -7314,10 +7487,12 @@ chat item action
Set member admission
+ 设置成员入群准许
No comment provided by engineer.
Set message expiration in chats.
+ 在聊天中设置消息过期时间。
No comment provided by engineer.
@@ -7337,6 +7512,7 @@ chat item action
Set profile bio and welcome message.
+ 设置自我介绍和欢迎消息。
No comment provided by engineer.
@@ -7356,6 +7532,7 @@ chat item action
Settings were changed.
+ 设置已修改。
alert message
@@ -7376,10 +7553,12 @@ chat item action
Share 1-time link with a friend
+ 和一位好友分享一次性链接
No comment provided by engineer.
Share SimpleX address on social media.
+ 在社媒上分享 SimpleX 地址。
No comment provided by engineer.
@@ -7389,6 +7568,7 @@ chat item action
Share address publicly
+ 公开分享地址
No comment provided by engineer.
@@ -7408,14 +7588,17 @@ chat item action
Share old address
+ 分享旧地址
alert button
Share old link
+ 分享旧链接
alert button
Share profile
+ 分享资料
No comment provided by engineer.
@@ -7435,18 +7618,22 @@ chat item action
Share your address
+ 分享地址
No comment provided by engineer.
Short SimpleX address
+ SimpleX 短地址
No comment provided by engineer.
Short description
+ 短描述
No comment provided by engineer.
Short link
+ 短链接
No comment provided by engineer.
@@ -7601,6 +7788,7 @@ chat item action
SimpleX relay link
+ SimpleX 中继链接
simplex link type
@@ -7656,6 +7844,8 @@ chat item action
Some servers failed the test:
%@
+ 有服务器测试未通过:
+%@
alert message
@@ -7665,6 +7855,7 @@ chat item action
Spam
+ 垃圾信息
blocking reason
report reason
@@ -7789,10 +7980,12 @@ report reason
Switch audio and video during the call.
+ 通话期间切换音频和视频。
No comment provided by engineer.
Switch chat profile for 1-time invitations.
+ 对一次性邀请切换聊天个人资料。
No comment provided by engineer.
@@ -7812,6 +8005,7 @@ report reason
TCP connection bg timeout
+ TCP 连接后台超时
No comment provided by engineer.
@@ -7821,6 +8015,7 @@ report reason
TCP port for messaging
+ 用于消息收发的 TCP 端口
No comment provided by engineer.
@@ -7840,6 +8035,7 @@ report reason
Tail
+ 尾部
No comment provided by engineer.
@@ -7849,22 +8045,27 @@ report reason
Tap Connect to chat
+ 轻按连接进行聊天
No comment provided by engineer.
Tap Connect to send request
+ 轻按连接来发送请求
No comment provided by engineer.
Tap Connect to use bot
+ 轻按“连接”使用机器人
No comment provided by engineer.
Tap Join group
+ 轻按加入群
No comment provided by engineer.
@@ -7914,6 +8115,7 @@ report reason
Test notifications
+ 测试通知
No comment provided by engineer.
@@ -7955,6 +8157,7 @@ It can happen because of some bug or when the connection is compromised.
The address will be short, and your profile will be shared via the address.
+ 地址不会长,将通过该简短地址分享个人资料。
alert message
@@ -7964,6 +8167,7 @@ It can happen because of some bug or when the connection is compromised.
The app protects your privacy by using different operators in each conversation.
+ 应用通过在每个对话中使用不同运营方保护你的隐私。
No comment provided by engineer.
@@ -7983,6 +8187,7 @@ It can happen because of some bug or when the connection is compromised.
The connection reached the limit of undelivered messages, your contact may be offline.
+ 连接达到了未送达消息上限,你的联系人可能处于离线状态。
No comment provided by engineer.
@@ -8017,6 +8222,7 @@ It can happen because of some bug or when the connection is compromised.
The link will be short, and group profile will be shared via the link.
+ 链接不会长,群资料会通过短链接分享。
alert message
@@ -8050,6 +8256,7 @@ It can happen because of some bug or when the connection is compromised.
The second preset operator in the app!
+ 应用中的第二个预设运营方!
No comment provided by engineer.
@@ -8078,6 +8285,7 @@ It can happen because of some bug or when the connection is compromised.
The uploaded database archive will be permanently removed from the servers.
+ 已上传的数据库归档将会从服务器中永久移除。
No comment provided by engineer.
@@ -8087,6 +8295,7 @@ It can happen because of some bug or when the connection is compromised.
These conditions will also apply for: **%@**.
+ 这些条件将同样适用于: **%@**。
No comment provided by engineer.
@@ -8111,6 +8320,7 @@ It can happen because of some bug or when the connection is compromised.
This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted.
+ 此操作无法撤销 —— 比此聊天中所选消息更早发出并收到的消息将被删除。
alert message
@@ -8150,6 +8360,7 @@ It can happen because of some bug or when the connection is compromised.
This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link.
+ 此链接需要更新的应用版本。请升级应用或请求你的联系人发送相容的链接。
No comment provided by engineer.
@@ -8159,6 +8370,7 @@ It can happen because of some bug or when the connection is compromised.
This message was deleted or not received yet.
+ 此消息被删除或尚未收到。
No comment provided by engineer.
@@ -8168,10 +8380,12 @@ It can happen because of some bug or when the connection is compromised.
This setting is for your current profile **%@**.
+ 此设置用于当前个人资料 **%@**。
No comment provided by engineer.
Time to disappear is set only for new contacts.
+ 只为新联系人设置了消失时间。
No comment provided by engineer.
@@ -8201,6 +8415,7 @@ It can happen because of some bug or when the connection is compromised.
To protect against your link being replaced, you can compare contact security codes.
+ 为了防止链接被替换,你可以比较联系人安全代码。
No comment provided by engineer.
@@ -8227,14 +8442,17 @@ You will be prompted to complete authentication before this feature is enabled.<
To receive
+ 消息接收
No comment provided by engineer.
To record speech please grant permission to use Microphone.
+ 为了记录语音请授予使用麦克风权限。
No comment provided by engineer.
To record video please grant permission to use Camera.
+ 为了录制视频请授予使用相机权限。
No comment provided by engineer.
@@ -8249,10 +8467,12 @@ You will be prompted to complete authentication before this feature is enabled.<
To send
+ 发送
No comment provided by engineer.
To send commands you must be connected.
+ 你必须已连接才能发送命令。
alert message
@@ -8262,10 +8482,12 @@ You will be prompted to complete authentication before this feature is enabled.<
To use another profile after connection attempt, delete the chat and use the link again.
+ 要在连接尝试后使用不同的个人资料,请删除聊天并再次使用该链接。
alert message
To use the servers of **%@**, accept conditions of use.
+ 要使用**%@**的服务器,需接受条款。
No comment provided by engineer.
@@ -8309,6 +8531,7 @@ You will be prompted to complete authentication before this feature is enabled.<
Trying to connect to the server used to receive messages from this connection.
+ 尝试连接到用于从该连接接收消息的服务器。
subscription status explanation
@@ -8358,6 +8581,7 @@ You will be prompted to complete authentication before this feature is enabled.<
Undelivered messages
+ 未送达的消息
No comment provided by engineer.
@@ -8454,6 +8678,7 @@ To connect, please ask your contact to create another connection link and check
Unsupported connection link
+ 不支持的连接链接
No comment provided by engineer.
@@ -8483,6 +8708,7 @@ To connect, please ask your contact to create another connection link and check
Updated conditions
+ 条款已更新
No comment provided by engineer.
@@ -8492,14 +8718,17 @@ To connect, please ask your contact to create another connection link and check
Upgrade
+ 升级
alert button
Upgrade address
+ 升级地址
No comment provided by engineer.
Upgrade address?
+ 升级地址?
alert message
@@ -8509,14 +8738,17 @@ To connect, please ask your contact to create another connection link and check
Upgrade group link?
+ 升级群链接?
alert message
Upgrade link
+ 升级链接
No comment provided by engineer.
Upgrade your address
+ 升级你的地址
No comment provided by engineer.
@@ -8551,6 +8783,7 @@ To connect, please ask your contact to create another connection link and check
Use %@
+ 使用 %@
No comment provided by engineer.
@@ -8560,6 +8793,7 @@ To connect, please ask your contact to create another connection link and check
Use SOCKS proxy
+ 使用 SOCKS 代理
No comment provided by engineer.
@@ -8569,10 +8803,12 @@ To connect, please ask your contact to create another connection link and check
Use TCP port %@ when no port is specified.
+ 当未指定端口时使用TCP端口%@。
No comment provided by engineer.
Use TCP port 443 for preset servers only.
+ 仅预设服务器使用 TCP 协议 443 端口。
No comment provided by engineer.
@@ -8587,10 +8823,12 @@ To connect, please ask your contact to create another connection link and check
Use for files
+ 用于文件
No comment provided by engineer.
Use for messages
+ 用于消息
No comment provided by engineer.
@@ -8610,6 +8848,7 @@ To connect, please ask your contact to create another connection link and check
Use incognito profile
+ 使用隐身个人资料
No comment provided by engineer.
@@ -8639,6 +8878,7 @@ To connect, please ask your contact to create another connection link and check
Use servers
+ 使用服务器
No comment provided by engineer.
@@ -8653,6 +8893,7 @@ To connect, please ask your contact to create another connection link and check
Use web port
+ 使用 web 端口
No comment provided by engineer.
@@ -8662,6 +8903,7 @@ To connect, please ask your contact to create another connection link and check
Username
+ 用户名
No comment provided by engineer.
@@ -8729,6 +8971,10 @@ To connect, please ask your contact to create another connection link and check
视频将在您的联系人在线时收到,请稍等或稍后查看!
No comment provided by engineer.
+
+ Videos
+ No comment provided by engineer.
+
Videos and files up to 1gb
最大 1gb 的视频和文件
@@ -8736,6 +8982,7 @@ To connect, please ask your contact to create another connection link and check
View conditions
+ 查看条款
No comment provided by engineer.
@@ -8745,6 +8992,7 @@ To connect, please ask your contact to create another connection link and check
View updated conditions
+ 查看更新后的条款
No comment provided by engineer.
@@ -8844,6 +9092,7 @@ To connect, please ask your contact to create another connection link and check
Welcome your contacts 👋
+ 欢迎联系人👋
No comment provided by engineer.
@@ -8863,6 +9112,7 @@ To connect, please ask your contact to create another connection link and check
When more than one operator is enabled, none of them has metadata to learn who communicates with whom.
+ 当启用了超过一个运营方时,没有一个运营方拥有了解谁和谁联络的元数据。
No comment provided by engineer.
@@ -8962,6 +9212,7 @@ To connect, please ask your contact to create another connection link and check
You are already connected with %@.
+ 你已经与%@保持连接。
No comment provided by engineer.
@@ -8998,6 +9249,7 @@ Repeat join request?
You are connected to the server used to receive messages from this connection.
+ 你已连接到用于接收该连接消息的服务器。
subscription status explanation
@@ -9007,6 +9259,7 @@ Repeat join request?
You are not connected to the server used to receive messages from this connection (no subscription).
+ 未连接到用于从该连接接收消息的服务器(无订阅)。
subscription status explanation
@@ -9026,6 +9279,7 @@ Repeat join request?
You can configure servers via settings.
+ 你可以通过设置配置服务器。
No comment provided by engineer.
@@ -9070,6 +9324,7 @@ Repeat join request?
You can set connection name, to remember who the link was shared with.
+ 你可以设置连接名称,用来记住和谁分享了这个链接。
No comment provided by engineer.
@@ -9114,6 +9369,7 @@ Repeat join request?
You can view your reports in Chat with admins.
+ 你可以在和管理员和聊天中查看你的举报。
alert message
@@ -9199,6 +9455,7 @@ Repeat connection request?
You will be able to send messages **only after your request is accepted**.
+ **只有在你的请求被接受后**你才能发送消息。
No comment provided by engineer.
@@ -9233,6 +9490,7 @@ Repeat connection request?
You will stop receiving messages from this chat. Chat history will be preserved.
+ 你将停止从这个聊天收到消息。聊天历史将被保留。
No comment provided by engineer.
@@ -9267,6 +9525,7 @@ Repeat connection request?
Your business contact
+ 你的企业联系人
No comment provided by engineer.
@@ -9286,6 +9545,7 @@ Repeat connection request?
Your chat preferences
+ 你的聊天偏好设置
alert title
@@ -9303,6 +9563,7 @@ Repeat connection request?
Your contact
+ 你的联系人
No comment provided by engineer.
@@ -9322,6 +9583,7 @@ Repeat connection request?
Your credentials may be sent unencrypted.
+ 你的凭据可能以未经加密的方式被发送。
No comment provided by engineer.
@@ -9336,6 +9598,7 @@ Repeat connection request?
Your group
+ 你的群
No comment provided by engineer.
@@ -9370,6 +9633,7 @@ Repeat connection request?
Your profile was changed. If you save it, the updated profile will be sent to all your contacts.
+ 您的个人资料已修改。如果进行保存,更新后的个人资料将发送到所有联系人。
alert message
@@ -9384,6 +9648,7 @@ Repeat connection request?
Your servers
+ 你的服务器
No comment provided by engineer.
@@ -9432,10 +9697,12 @@ Repeat connection request?
accepted invitation
+ 已接受邀请
chat list item title
accepted you
+ 接受了你
rcv group event chat item
@@ -9460,6 +9727,7 @@ Repeat connection request?
all
+ 全部
member criteria value
@@ -9479,6 +9747,7 @@ Repeat connection request?
archived report
+ 已存档的举报
No comment provided by engineer.
@@ -9549,6 +9818,7 @@ marked deleted chat item preview text
can't send messages
+ 无法发送消息
No comment provided by engineer.
@@ -9653,10 +9923,12 @@ marked deleted chat item preview text
contact deleted
+ 删除了联系人
No comment provided by engineer.
contact disabled
+ 禁用了联系人
No comment provided by engineer.
@@ -9671,10 +9943,12 @@ marked deleted chat item preview text
contact not ready
+ 联系人未就绪
No comment provided by engineer.
contact should accept…
+ 联系人应当接受…
No comment provided by engineer.
@@ -9845,6 +10119,7 @@ pref value
group
+ 群
shown on group welcome message
@@ -9854,6 +10129,7 @@ pref value
group is deleted
+ 群被删除了
No comment provided by engineer.
@@ -9978,6 +10254,7 @@ pref value
member has old version
+ 成员有旧版本
No comment provided by engineer.
@@ -10012,6 +10289,7 @@ pref value
moderator
+ 协管
member role
@@ -10041,6 +10319,7 @@ pref value
no subscription
+ 无订阅
No comment provided by engineer.
@@ -10050,6 +10329,7 @@ pref value
not synchronized
+ 未同步
No comment provided by engineer.
@@ -10111,10 +10391,12 @@ time to disappear
pending approval
+ 待批准
No comment provided by engineer.
pending review
+ 待审核
No comment provided by engineer.
@@ -10134,6 +10416,7 @@ time to disappear
rejected
+ 被拒绝
No comment provided by engineer.
@@ -10158,6 +10441,7 @@ time to disappear
removed from group
+ 从群被删除了
No comment provided by engineer.
@@ -10172,30 +10456,37 @@ time to disappear
request is sent
+ 发送了请求
No comment provided by engineer.
request to join rejected
+ 加入请求被拒绝
No comment provided by engineer.
requested connection
+ 已请求连接
rcv group event chat item
requested connection from group %@
+ 来自群组%@的已请求连接
rcv direct event chat item
requested to connect
+ 被请求连接
chat list item title
review
+ 审核
No comment provided by engineer.
reviewed by admins
+ 由管理员审核
No comment provided by engineer.
@@ -10384,6 +10675,7 @@ last received msg: %2$@
you accepted this member
+ 你接受了该成员
snd group event chat item
@@ -10519,22 +10811,27 @@ last received msg: %2$@
%d new events
+ %d条新事件
notification body
From %d chat(s)
+ 来自 %d 条聊天
notification body
From: %@
+ 来自: %@
notification body
New events
+ 新事件
notification
New messages
+ 新消息
notification
diff --git a/apps/ios/SimpleX NSE/NotificationService.swift b/apps/ios/SimpleX NSE/NotificationService.swift
index 5d619ac130..25df063f82 100644
--- a/apps/ios/SimpleX NSE/NotificationService.swift
+++ b/apps/ios/SimpleX NSE/NotificationService.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 26/04/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/services/notifications.md
import UserNotifications
import OSLog
@@ -22,6 +23,7 @@ let nseSuspendSchedule: SuspendSchedule = (2, 4)
let fastNSESuspendSchedule: SuspendSchedule = (1, 1)
+// Spec: spec/services/notifications.md#NSENotificationData
public enum NSENotificationData {
case connectionEvent(_ user: User, _ connEntity: ConnectionEntity)
case contactConnected(_ user: any UserLike, _ contact: Contact)
@@ -76,6 +78,7 @@ public enum NSENotificationData {
// Once the last thread in the process completes processing chat controller is suspended, and the database is closed, to avoid
// background crashes and contention for database with the application (both UI and background fetch triggered either on schedule
// or when background notification is received.
+// Spec: spec/services/notifications.md#NSEThreads
class NSEThreads {
static let shared = NSEThreads()
private let queue = DispatchQueue(label: "chat.simplex.app.SimpleX-NSE.notification-threads.lock")
@@ -238,6 +241,7 @@ class NSEThreads {
// NotificationEntities for the same connection across multiple NSE instances (NSEThreads) are processed sequentially, so that the earliest NSE instance receives the earliest messages.
// The reason for this complexity is to process all required messages within allotted 30 seconds,
// accounting for the possibility that multiple notifications may be delivered concurrently.
+// Spec: spec/services/notifications.md#NotificationEntity
struct NotificationEntity {
var ntfConn: NtfConn
var entityId: ChatId
@@ -279,6 +283,7 @@ struct NotificationEntity {
// Each didReceive is called in its own thread, but multiple calls can be made in one process, and, empirically, there is never
// more than one process of notification service extension exists at a time.
// Soon after notification service delivers the last notification it is either suspended or terminated.
+// Spec: spec/services/notifications.md#NotificationService
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
// served as notification if no message attempts (msgBestAttemptNtf) could be produced
@@ -291,6 +296,7 @@ class NotificationService: UNNotificationServiceExtension {
var appSubscriber: AppSubscriber?
var returnedSuspension = false
+ // Spec: spec/services/notifications.md#didReceive
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
logger.debug("DEBUGGING: NotificationService.didReceive")
let receivedNtf = if let ntf_ = request.content.mutableCopy() as? UNMutableNotificationContent { ntf_ } else { UNMutableNotificationContent() }
@@ -594,6 +600,7 @@ class NotificationService: UNNotificationServiceExtension {
serviceBestAttemptNtf = ntf
}
+ // Spec: spec/services/notifications.md#deliverBestAttemptNtf
private func deliverBestAttemptNtf(urgent: Bool = false) {
logger.debug("NotificationService.deliverBestAttemptNtf urgent: \(urgent) expectingMoreMessages: \(self.expectingMoreMessages)")
if let handler = contentHandler, urgent || !expectingMoreMessages {
@@ -770,6 +777,7 @@ class NotificationService: UNNotificationServiceExtension {
}
// nseStateGroupDefault must not be used in NSE directly, only via this singleton
+// Spec: spec/services/notifications.md#NSEChatState
class NSEChatState {
static let shared = NSEChatState()
private var value_ = NSEState.created
@@ -824,6 +832,7 @@ var networkConfig: NetCfg = getNetCfg()
// startChat uses semaphore startLock to ensure that only one didReceive thread can start chat controller
// Subsequent calls to didReceive will be waiting on semaphore and won't start chat again, as it will be .active
+ // Spec: spec/services/notifications.md#startChat-NSE
func startChat() -> DBMigrationResult? {
logger.debug("NotificationService: startChat")
// only skip creating if there is chat controller
@@ -848,6 +857,7 @@ func startChat() -> DBMigrationResult? {
}
}
+ // Spec: spec/services/notifications.md#doStartChat
func doStartChat() -> DBMigrationResult? {
logger.debug("NotificationService: doStartChat")
haskell_init_nse()
@@ -940,6 +950,7 @@ func chatSuspended() {
// A single loop is used per Notification service extension process to receive and process all messages depending on the NSE state
// If the extension is not active yet, or suspended/suspending, or the app is running, the notifications will not be received.
+ // Spec: spec/services/notifications.md#receiveMessages
func receiveMessages() async {
logger.debug("NotificationService receiveMessages")
while true {
@@ -988,6 +999,7 @@ private let isInChina = SKStorefront().countryCode == "CHN"
private func useCallKit() -> Bool { !isInChina && callKitEnabledGroupDefault.get() }
@inline(__always)
+ // Spec: spec/services/notifications.md#receivedMsgNtf
func receivedMsgNtf(_ res: NSEChatEvent) async -> (String, NSENotificationData)? {
logger.debug("NotificationService receivedMsgNtf: \(res.responseType)")
switch res {
diff --git a/apps/ios/SimpleX NSE/zh-Hans.lproj/Localizable.strings b/apps/ios/SimpleX NSE/zh-Hans.lproj/Localizable.strings
index 5ef592ec70..4e4b130fa4 100644
--- a/apps/ios/SimpleX NSE/zh-Hans.lproj/Localizable.strings
+++ b/apps/ios/SimpleX NSE/zh-Hans.lproj/Localizable.strings
@@ -1,7 +1,15 @@
-/*
- Localizable.strings
- SimpleX
+/* notification body */
+"%d new events" = "%d条新事件";
+
+/* notification body */
+"From %d chat(s)" = "来自 %d 条聊天";
+
+/* notification body */
+"From: %@" = "来自: %@";
+
+/* notification */
+"New events" = "新事件";
+
+/* notification */
+"New messages" = "新消息";
- Created by EP on 30/07/2024.
- Copyright © 2024 SimpleX Chat. All rights reserved.
-*/
diff --git a/apps/ios/SimpleX SE/hu.lproj/Localizable.strings b/apps/ios/SimpleX SE/hu.lproj/Localizable.strings
index 2fedf0e6f1..dfb7a302b9 100644
--- a/apps/ios/SimpleX SE/hu.lproj/Localizable.strings
+++ b/apps/ios/SimpleX SE/hu.lproj/Localizable.strings
@@ -14,10 +14,10 @@
"Cannot forward message" = "Nem lehet továbbítani az üzenetet";
/* No comment provided by engineer. */
-"Comment" = "Hozzászólás";
+"Comment" = "Megjegyzés";
/* No comment provided by engineer. */
-"Currently maximum supported file size is %@." = "Jelenleg támogatott legnagyobb fájl méret: %@.";
+"Currently maximum supported file size is %@." = "Jelenleg támogatott legnagyobb fájlméret: %@.";
/* No comment provided by engineer. */
"Database downgrade required" = "Adatbázis visszafejlesztése szükséges";
@@ -80,7 +80,7 @@
"Please create a profile in the SimpleX app" = "Hozzon létre egy profilt a SimpleX alkalmazásban";
/* No comment provided by engineer. */
-"Selected chat preferences prohibit this message." = "A kijelölt csevegési beállítások tiltják ezt az üzenetet.";
+"Selected chat preferences prohibit this message." = "A kiválasztott csevegési beállítások tiltják ezt az üzenetet.";
/* No comment provided by engineer. */
"Sending a message takes longer than expected." = "Az üzenet elküldése a vártnál tovább tart.";
diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj
index fa9a4efdf7..9265138c53 100644
--- a/apps/ios/SimpleX.xcodeproj/project.pbxproj
+++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj
@@ -178,8 +178,8 @@
64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; };
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; };
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; };
- 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5-ghc9.6.3.a */; };
- 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5.a */; };
+ 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo-ghc9.6.3.a */; };
+ 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo.a */; };
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; };
64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; };
64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; };
@@ -545,8 +545,8 @@
64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; };
64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; };
64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; };
- 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5-ghc9.6.3.a"; sourceTree = ""; };
- 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5.a"; sourceTree = ""; };
+ 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo-ghc9.6.3.a"; sourceTree = ""; };
+ 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo.a"; sourceTree = ""; };
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; };
64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; };
64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; };
@@ -708,8 +708,8 @@
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */,
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */,
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */,
- 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5-ghc9.6.3.a in Frameworks */,
- 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5.a in Frameworks */,
+ 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo-ghc9.6.3.a in Frameworks */,
+ 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo.a in Frameworks */,
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -795,8 +795,8 @@
64C829992D54AEEE006B9E89 /* libffi.a */,
64C829982D54AEED006B9E89 /* libgmp.a */,
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */,
- 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5-ghc9.6.3.a */,
- 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5.a */,
+ 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo-ghc9.6.3.a */,
+ 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.9-IckAKQLBKZZ3c4EBa1qhzo.a */,
);
path = Libraries;
sourceTree = "";
@@ -2003,7 +2003,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 318;
+ CURRENT_PROJECT_VERSION = 321;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
@@ -2053,7 +2053,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 318;
+ CURRENT_PROJECT_VERSION = 321;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
@@ -2095,7 +2095,7 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 318;
+ CURRENT_PROJECT_VERSION = 321;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@@ -2115,7 +2115,7 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 318;
+ CURRENT_PROJECT_VERSION = 321;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@@ -2140,7 +2140,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 318;
+ CURRENT_PROJECT_VERSION = 321;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
GCC_OPTIMIZATION_LEVEL = s;
@@ -2177,7 +2177,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 318;
+ CURRENT_PROJECT_VERSION = 321;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
ENABLE_CODE_COVERAGE = NO;
@@ -2214,7 +2214,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 318;
+ CURRENT_PROJECT_VERSION = 321;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -2265,7 +2265,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 318;
+ CURRENT_PROJECT_VERSION = 321;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -2316,7 +2316,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 318;
+ CURRENT_PROJECT_VERSION = 321;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -2350,7 +2350,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 318;
+ CURRENT_PROJECT_VERSION = 321;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
diff --git a/apps/ios/SimpleXChat/API.swift b/apps/ios/SimpleXChat/API.swift
index 40cee93faf..85c84a6f45 100644
--- a/apps/ios/SimpleXChat/API.swift
+++ b/apps/ios/SimpleXChat/API.swift
@@ -110,6 +110,7 @@ public func resetChatCtrl() {
migrationResult = nil
}
+// Spec: spec/api.md#sendSimpleXCmd
@inline(__always)
public func sendSimpleXCmd(_ cmd: ChatCmdProtocol, _ ctrl: chat_ctrl? = nil, retryNum: Int32 = 0) -> APIResult {
if let d = sendSimpleXCmdStr(cmd.cmdString, ctrl, retryNum: retryNum) {
diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift
index fce0f100f2..b31a799e68 100644
--- a/apps/ios/SimpleXChat/APITypes.swift
+++ b/apps/ios/SimpleXChat/APITypes.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 26/04/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/api.md
import Foundation
import SwiftUI
@@ -22,6 +23,7 @@ public func onOff(_ b: Bool) -> String {
b ? "on" : "off"
}
+// Spec: spec/api.md#APIResult
public enum APIResult: Decodable where R: Decodable, R: ChatAPIResult {
case result(R)
case error(ChatError)
@@ -59,6 +61,7 @@ public enum APIResult: Decodable where R: Decodable, R: ChatAPIResult {
}
}
+// Spec: spec/api.md#ChatAPIResult
public protocol ChatAPIResult: Decodable {
var responseType: String { get }
var details: String { get }
@@ -79,6 +82,7 @@ extension ChatAPIResult {
}
}
+// Spec: spec/api.md#decodeAPIResult
public func decodeAPIResult(_ d: Data) -> APIResult {
// print("decodeAPIResult \(String(describing: R.self))")
do {
@@ -691,6 +695,7 @@ private func encodeCJSON(_ value: T) -> [CChar] {
encodeJSON(value).cString(using: .utf8)!
}
+// Spec: spec/api.md#ChatError
public enum ChatError: Decodable, Hashable, Error {
case error(errorType: ChatErrorType)
case errorAgent(agentError: AgentErrorType)
@@ -713,6 +718,7 @@ public enum ChatError: Decodable, Hashable, Error {
}
}
+// Spec: spec/api.md#ChatErrorType
public enum ChatErrorType: Decodable, Hashable {
case noActiveUser
case noConnectionUser(agentConnId: String)
diff --git a/apps/ios/SimpleXChat/CallTypes.swift b/apps/ios/SimpleXChat/CallTypes.swift
index da1720c134..ece65130e6 100644
--- a/apps/ios/SimpleXChat/CallTypes.swift
+++ b/apps/ios/SimpleXChat/CallTypes.swift
@@ -5,10 +5,12 @@
// Created by Evgeny on 05/05/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/services/calls.md
import Foundation
import SwiftUI
+// Spec: spec/services/calls.md#WebRTCCallOffer
public struct WebRTCCallOffer: Encodable {
public init(callType: CallType, rtcSession: WebRTCSession) {
self.callType = callType
@@ -19,6 +21,7 @@ public struct WebRTCCallOffer: Encodable {
public var rtcSession: WebRTCSession
}
+// Spec: spec/services/calls.md#WebRTCSession
public struct WebRTCSession: Codable {
public init(rtcSession: String, rtcIceCandidates: String) {
self.rtcSession = rtcSession
@@ -29,6 +32,7 @@ public struct WebRTCSession: Codable {
public var rtcIceCandidates: String
}
+// Spec: spec/services/calls.md#WebRTCExtraInfo
public struct WebRTCExtraInfo: Codable {
public init(rtcIceCandidates: String) {
self.rtcIceCandidates = rtcIceCandidates
@@ -37,6 +41,7 @@ public struct WebRTCExtraInfo: Codable {
public var rtcIceCandidates: String
}
+// Spec: spec/services/calls.md#RcvCallInvitation
public struct RcvCallInvitation: Decodable {
public var user: User
public var contact: Contact
@@ -65,6 +70,7 @@ public struct RcvCallInvitation: Decodable {
)
}
+// Spec: spec/services/calls.md#CallType
public struct CallType: Codable {
public init(media: CallMediaType, capabilities: CallCapabilities) {
self.media = media
diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift
index 5d1d5b4302..d95e5233c1 100644
--- a/apps/ios/SimpleXChat/ChatTypes.swift
+++ b/apps/ios/SimpleXChat/ChatTypes.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 26/04/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/state.md | spec/api.md
import Foundation
import SwiftUI
@@ -1367,6 +1368,7 @@ public enum GroupFeatureEnabled: String, Codable, Identifiable, Hashable {
}
}
+// Spec: spec/state.md#ChatInfo
public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
case direct(contact: Contact)
case group(groupInfo: GroupInfo, groupChatScope: GroupChatScopeInfo?)
@@ -1871,6 +1873,7 @@ public struct ChatData: Decodable, Identifiable, Hashable, ChatLike {
}
}
+// Spec: spec/state.md#ChatStats
public struct ChatStats: Decodable, Hashable {
public init(
unreadCount: Int = 0,
@@ -2110,6 +2113,11 @@ public struct Connection: Decodable, Hashable {
public var id: ChatId { get { ":\(connId)" } }
+ public var connFailedErr: String? {
+ if case let .failed(err) = connStatus { return err }
+ return nil
+ }
+
public var connDisabled: Bool {
authErrCounter >= 10 // authErrDisableCount in core
}
@@ -2295,15 +2303,16 @@ public struct PendingContactConnection: Decodable, NamedChat, Hashable {
}
}
-public enum ConnStatus: String, Decodable, Hashable {
- case new = "new"
- case prepared = "prepared"
- case joined = "joined"
- case requested = "requested"
- case accepted = "accepted"
- case sndReady = "snd-ready"
- case ready = "ready"
- case deleted = "deleted"
+public enum ConnStatus: Decodable, Hashable {
+ case new
+ case prepared
+ case joined
+ case requested
+ case accepted
+ case sndReady
+ case ready
+ case deleted
+ case failed(connError: String)
var initiated: Bool? {
get {
@@ -2316,6 +2325,7 @@ public enum ConnStatus: String, Decodable, Hashable {
case .sndReady: return nil
case .ready: return nil
case .deleted: return nil
+ case .failed: return nil
}
}
}
@@ -4234,6 +4244,7 @@ public struct CIFile: Decodable, Hashable {
}
}
+// Spec: spec/services/files.md#CryptoFile
public struct CryptoFile: Codable, Hashable {
public var filePath: String // the name of the file, not a full path
public var cryptoArgs: CryptoFileArgs?
@@ -4281,6 +4292,7 @@ public struct CryptoFile: Codable, Hashable {
static var decryptedUrls = Dictionary()
}
+// Spec: spec/services/files.md#CryptoFileArgs
public struct CryptoFileArgs: Codable, Hashable {
public var fileKey: String
public var fileNonce: String
@@ -4601,7 +4613,7 @@ extension MsgContent: Encodable {
}
}
-public enum MsgContentTag: String {
+public enum MsgContentTag: String, Decodable {
case text
case link
case image
@@ -4651,6 +4663,7 @@ public enum Format: Decodable, Equatable, Hashable {
case strikeThrough
case snippet
case secret
+ case small
case colored(color: FormatColor)
case uri
case hyperLink(showText: String?, linkUri: String)
diff --git a/apps/ios/SimpleXChat/CryptoFile.swift b/apps/ios/SimpleXChat/CryptoFile.swift
index dfe833f832..5a0d48dced 100644
--- a/apps/ios/SimpleXChat/CryptoFile.swift
+++ b/apps/ios/SimpleXChat/CryptoFile.swift
@@ -4,6 +4,7 @@
//
// Created by Evgeny on 05/09/2023.
// Copyright © 2023 SimpleX Chat. All rights reserved.
+// Spec: spec/services/files.md
//
import Foundation
@@ -13,6 +14,7 @@ enum WriteFileResult: Decodable {
case error(writeError: String)
}
+// Spec: spec/services/files.md#writeCryptoFile
public func writeCryptoFile(path: String, data: Data) throws -> CryptoFileArgs {
let ptr: UnsafeMutableRawPointer = malloc(data.count)
memcpy(ptr, (data as NSData).bytes, data.count)
@@ -25,6 +27,7 @@ public func writeCryptoFile(path: String, data: Data) throws -> CryptoFileArgs {
}
}
+// Spec: spec/services/files.md#readCryptoFile
public func readCryptoFile(path: String, cryptoArgs: CryptoFileArgs) throws -> Data {
var cPath = path.cString(using: .utf8)!
var cKey = cryptoArgs.fileKey.cString(using: .utf8)!
@@ -47,6 +50,7 @@ public func readCryptoFile(path: String, cryptoArgs: CryptoFileArgs) throws -> D
}
}
+// Spec: spec/services/files.md#encryptCryptoFile
public func encryptCryptoFile(fromPath: String, toPath: String) throws -> CryptoFileArgs {
var cFromPath = fromPath.cString(using: .utf8)!
var cToPath = toPath.cString(using: .utf8)!
@@ -58,6 +62,7 @@ public func encryptCryptoFile(fromPath: String, toPath: String) throws -> Crypto
}
}
+// Spec: spec/services/files.md#decryptCryptoFile
public func decryptCryptoFile(fromPath: String, cryptoArgs: CryptoFileArgs, toPath: String) throws {
var cFromPath = fromPath.cString(using: .utf8)!
var cKey = cryptoArgs.fileKey.cString(using: .utf8)!
diff --git a/apps/ios/SimpleXChat/FileUtils.swift b/apps/ios/SimpleXChat/FileUtils.swift
index 2341eb4a4f..3d0dd663c1 100644
--- a/apps/ios/SimpleXChat/FileUtils.swift
+++ b/apps/ios/SimpleXChat/FileUtils.swift
@@ -5,6 +5,7 @@
// Created by JRoberts on 15.04.2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/services/files.md
import Foundation
import OSLog
@@ -13,14 +14,19 @@ import UIKit
let logger = Logger()
// image file size for complession
+// Spec: spec/services/files.md#MAX_IMAGE_SIZE
public let MAX_IMAGE_SIZE: Int64 = 261_120 // 255KB
+// Spec: spec/services/files.md#MAX_IMAGE_SIZE_AUTO_RCV
public let MAX_IMAGE_SIZE_AUTO_RCV: Int64 = MAX_IMAGE_SIZE * 2
+// Spec: spec/services/files.md#MAX_VOICE_SIZE_AUTO_RCV
public let MAX_VOICE_SIZE_AUTO_RCV: Int64 = MAX_IMAGE_SIZE * 2
+// Spec: spec/services/files.md#MAX_VIDEO_SIZE_AUTO_RCV
public let MAX_VIDEO_SIZE_AUTO_RCV: Int64 = 1_047_552 // 1023KB
+// Spec: spec/services/files.md#MAX_FILE_SIZE_XFTP
public let MAX_FILE_SIZE_XFTP: Int64 = 1_073_741_824 // 1GB
public let MAX_FILE_SIZE_LOCAL: Int64 = Int64.max
@@ -37,10 +43,12 @@ private let CHAT_DB_BAK: String = "_chat.db.bak"
private let AGENT_DB_BAK: String = "_agent.db.bak"
+// Spec: spec/database.md#getDocumentsDirectory
public func getDocumentsDirectory() -> URL {
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
}
+// Spec: spec/database.md#getGroupContainerDirectory
public func getGroupContainerDirectory() -> URL {
FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: APP_GROUP_NAME)!
}
@@ -51,12 +59,14 @@ func getAppDirectory() -> URL {
: getDocumentsDirectory()
}
+// Spec: spec/database.md#DB_FILE_PREFIX
let DB_FILE_PREFIX = "simplex_v1"
func getLegacyDatabasePath() -> URL {
getDocumentsDirectory().appendingPathComponent("mobile_v1", isDirectory: false)
}
+// Spec: spec/database.md#getAppDatabasePath
public func getAppDatabasePath() -> URL {
dbContainerGroupDefault.get() == .group
? getGroupContainerDirectory().appendingPathComponent(DB_FILE_PREFIX, isDirectory: false)
@@ -72,6 +82,7 @@ func fileModificationDate(_ path: String) -> Date? {
}
}
+// Spec: spec/services/files.md#deleteAppDatabaseAndFiles
public func deleteAppDatabaseAndFiles() {
let fm = FileManager.default
let dbPath = getAppDatabasePath().path
@@ -93,6 +104,7 @@ public func deleteAppDatabaseAndFiles() {
storeDBPassphraseGroupDefault.set(true)
}
+// Spec: spec/services/files.md#deleteAppFiles
public func deleteAppFiles() {
let fm = FileManager.default
do {
@@ -183,6 +195,7 @@ public func removeLegacyDatabaseAndFiles() -> Bool {
return r1 && r2
}
+// Spec: spec/services/files.md#getTempFilesDirectory
public func getTempFilesDirectory() -> URL {
getAppDirectory().appendingPathComponent("temp_files", isDirectory: true)
}
@@ -191,6 +204,7 @@ public func getMigrationTempFilesDirectory() -> URL {
getDocumentsDirectory().appendingPathComponent("migration_temp_files", isDirectory: true)
}
+// Spec: spec/services/files.md#getAppFilesDirectory
public func getAppFilesDirectory() -> URL {
getAppDirectory().appendingPathComponent("app_files", isDirectory: true)
}
@@ -199,6 +213,7 @@ public func getAppFilePath(_ fileName: String) -> URL {
getAppFilesDirectory().appendingPathComponent(fileName)
}
+// Spec: spec/services/files.md#getWallpaperDirectory
public func getWallpaperDirectory() -> URL {
getAppDirectory().appendingPathComponent("assets", isDirectory: true).appendingPathComponent("wallpapers", isDirectory: true)
}
@@ -207,6 +222,7 @@ public func getWallpaperFilePath(_ filename: String) -> URL {
getWallpaperDirectory().appendingPathComponent(filename)
}
+// Spec: spec/services/files.md#saveFile
public func saveFile(_ data: Data, _ fileName: String, encrypted: Bool) -> CryptoFile? {
let filePath = getAppFilePath(fileName)
do {
@@ -223,6 +239,7 @@ public func saveFile(_ data: Data, _ fileName: String, encrypted: Bool) -> Crypt
}
}
+// Spec: spec/services/files.md#removeFile
public func removeFile(_ url: URL) {
do {
try FileManager.default.removeItem(atPath: url.path)
@@ -239,12 +256,14 @@ public func removeFile(_ fileName: String) {
}
}
+// Spec: spec/services/files.md#cleanupDirectFile
public func cleanupDirectFile(_ aChatItem: AChatItem) {
if aChatItem.chatInfo.chatType == .direct {
cleanupFile(aChatItem)
}
}
+// Spec: spec/services/files.md#cleanupFile
public func cleanupFile(_ aChatItem: AChatItem) {
let cItem = aChatItem.chatItem
let mc = cItem.content.msgContent
diff --git a/apps/ios/SimpleXChat/Notifications.swift b/apps/ios/SimpleXChat/Notifications.swift
index 31b7ef83ff..24dc58202a 100644
--- a/apps/ios/SimpleXChat/Notifications.swift
+++ b/apps/ios/SimpleXChat/Notifications.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 28/04/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/services/notifications.md
import Foundation
import UserNotifications
@@ -22,6 +23,7 @@ public let appNotificationId = "chat.simplex.app.notification"
let contactHidden = NSLocalizedString("Contact hidden:", comment: "notification")
+// Spec: spec/services/notifications.md#createContactRequestNtf
public func createContactRequestNtf(_ user: any UserLike, _ contactRequest: UserContactRequest, _ badgeCount: Int) -> UNMutableNotificationContent {
let hideContent = ntfPreviewModeGroupDefault.get() == .hidden
return createNotification(
@@ -40,6 +42,7 @@ public func createContactRequestNtf(_ user: any UserLike, _ contactRequest: User
)
}
+// Spec: spec/services/notifications.md#createContactConnectedNtf
public func createContactConnectedNtf(_ user: any UserLike, _ contact: Contact, _ badgeCount: Int) -> UNMutableNotificationContent {
let hideContent = ntfPreviewModeGroupDefault.get() == .hidden
return createNotification(
@@ -59,6 +62,7 @@ public func createContactConnectedNtf(_ user: any UserLike, _ contact: Contact,
)
}
+// Spec: spec/services/notifications.md#createMessageReceivedNtf
public func createMessageReceivedNtf(_ user: any UserLike, _ cInfo: ChatInfo, _ cItem: ChatItem, _ badgeCount: Int) -> UNMutableNotificationContent {
let previewMode = ntfPreviewModeGroupDefault.get()
var title: String
@@ -78,6 +82,7 @@ public func createMessageReceivedNtf(_ user: any UserLike, _ cInfo: ChatInfo, _
)
}
+// Spec: spec/services/notifications.md#createCallInvitationNtf
public func createCallInvitationNtf(_ invitation: RcvCallInvitation, _ badgeCount: Int) -> UNMutableNotificationContent {
let text = invitation.callType.media == .video
? NSLocalizedString("Incoming video call", comment: "notification")
@@ -93,6 +98,7 @@ public func createCallInvitationNtf(_ invitation: RcvCallInvitation, _ badgeCoun
)
}
+// Spec: spec/services/notifications.md#createConnectionEventNtf
public func createConnectionEventNtf(_ user: User, _ connEntity: ConnectionEntity, _ badgeCount: Int) -> UNMutableNotificationContent {
let hideContent = ntfPreviewModeGroupDefault.get() == .hidden
var title: String
@@ -124,6 +130,7 @@ public func createConnectionEventNtf(_ user: User, _ connEntity: ConnectionEntit
)
}
+// Spec: spec/services/notifications.md#createErrorNtf
public func createErrorNtf(_ dbStatus: DBMigrationResult, _ badgeCount: Int) -> UNMutableNotificationContent {
var title: String
switch dbStatus {
@@ -149,6 +156,7 @@ public func createErrorNtf(_ dbStatus: DBMigrationResult, _ badgeCount: Int) ->
)
}
+// Spec: spec/services/notifications.md#createAppStoppedNtf
public func createAppStoppedNtf(_ badgeCount: Int) -> UNMutableNotificationContent {
return createNotification(
categoryIdentifier: ntfCategoryConnectionEvent,
@@ -163,6 +171,7 @@ private func groupMsgNtfTitle(_ groupInfo: GroupInfo, _ groupMember: GroupMember
: "#\(groupInfo.displayName) \(groupMember.chatViewName):"
}
+// Spec: spec/services/notifications.md#createNotification
public func createNotification(
categoryIdentifier: String,
title: String,
@@ -187,6 +196,7 @@ public func createNotification(
return content
}
+// Spec: spec/services/notifications.md#hideSecrets
func hideSecrets(_ cItem: ChatItem) -> String {
if let md = cItem.formattedText {
var res = ""
diff --git a/apps/ios/SimpleXChat/Theme/ChatWallpaperTypes.swift b/apps/ios/SimpleXChat/Theme/ChatWallpaperTypes.swift
index 662f8b43d1..2b64627dc2 100644
--- a/apps/ios/SimpleXChat/Theme/ChatWallpaperTypes.swift
+++ b/apps/ios/SimpleXChat/Theme/ChatWallpaperTypes.swift
@@ -9,6 +9,7 @@
import Foundation
import SwiftUI
+// Spec: spec/services/theme.md#PresetWallpaper
public enum PresetWallpaper: CaseIterable {
case cats
case flowers
@@ -306,6 +307,7 @@ public enum WallpaperScaleType: String, Codable, CaseIterable {
}
}
+// Spec: spec/services/theme.md#WallpaperType
public enum WallpaperType: Equatable {
public var image: SwiftUI.Image? {
if let uiImage {
diff --git a/apps/ios/SimpleXChat/Theme/ThemeTypes.swift b/apps/ios/SimpleXChat/Theme/ThemeTypes.swift
index 4074382543..a4e8050c6e 100644
--- a/apps/ios/SimpleXChat/Theme/ThemeTypes.swift
+++ b/apps/ios/SimpleXChat/Theme/ThemeTypes.swift
@@ -9,6 +9,7 @@
import Foundation
import SwiftUI
+// Spec: spec/services/theme.md#DefaultTheme
public enum DefaultTheme: String, Codable, Equatable {
case LIGHT
case DARK
@@ -39,6 +40,7 @@ public enum DefaultThemeMode: String, Codable {
case dark
}
+// Spec: spec/services/theme.md#Colors
public class Colors: ObservableObject, NSCopying, Equatable {
@Published public var primary: Color
@Published public var primaryVariant: Color
@@ -84,6 +86,7 @@ public class Colors: ObservableObject, NSCopying, Equatable {
public func clone() -> Colors { copy() as! Colors }
}
+// Spec: spec/services/theme.md#AppColors
public class AppColors: ObservableObject, NSCopying, Equatable {
@Published public var title: Color
@Published public var primaryVariant2: Color
@@ -135,6 +138,7 @@ public class AppColors: ObservableObject, NSCopying, Equatable {
}
}
+// Spec: spec/services/theme.md#AppWallpaper
public class AppWallpaper: ObservableObject, NSCopying, Equatable {
public static func == (lhs: AppWallpaper, rhs: AppWallpaper) -> Bool {
lhs.background == rhs.background &&
@@ -222,6 +226,7 @@ public enum ThemeColor {
}
}
+// Spec: spec/services/theme.md#ThemeColors
public struct ThemeColors: Codable, Equatable, Hashable {
public var primary: String? = nil
public var primaryVariant: String? = nil
@@ -293,6 +298,7 @@ public struct ThemeColors: Codable, Equatable, Hashable {
}
}
+// Spec: spec/services/theme.md#ThemeWallpaper
public struct ThemeWallpaper: Codable, Equatable, Hashable {
public var preset: String?
public var scale: Float?
@@ -375,6 +381,7 @@ public struct ThemeWallpaper: Codable, Equatable, Hashable {
/// If you add new properties, make sure they serialized to YAML correctly, see:
/// encodeThemeOverrides()
+// Spec: spec/services/theme.md#ThemeOverrides
public struct ThemeOverrides: Codable, Equatable, Hashable {
public var themeId: String = UUID().uuidString
public var base: DefaultTheme
@@ -559,6 +566,7 @@ extension [ThemeOverrides] {
}
+// Spec: spec/services/theme.md#ThemeModeOverrides
public struct ThemeModeOverrides: Codable, Hashable {
public var light: ThemeModeOverride? = nil
public var dark: ThemeModeOverride? = nil
@@ -573,6 +581,7 @@ public struct ThemeModeOverrides: Codable, Hashable {
}
}
+// Spec: spec/services/theme.md#ThemeModeOverride
public struct ThemeModeOverride: Codable, Equatable, Hashable {
public var mode: DefaultThemeMode// = CurrentColors.base.mode
public var colors: ThemeColors = ThemeColors()
diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings
index 038546f889..fb8529fb88 100644
--- a/apps/ios/bg.lproj/Localizable.strings
+++ b/apps/ios/bg.lproj/Localizable.strings
@@ -1613,7 +1613,8 @@ swipe action */
/* No comment provided by engineer. */
"Delete message?" = "Изтрий съобщението?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "Изтрий съобщенията";
/* No comment provided by engineer. */
@@ -2750,7 +2751,7 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "Ролята на члена ще бъде променена на \"%@\". Членът ще получи нова покана.";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "Членът ще бъде премахнат от групата - това не може да бъде отменено!";
/* No comment provided by engineer. */
@@ -3417,13 +3418,13 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "Relay сървърът защитава вашия IP адрес, но може да наблюдава продължителността на разговора.";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "Премахване";
/* No comment provided by engineer. */
"Remove member" = "Острани член";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "Острани член?";
/* No comment provided by engineer. */
diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings
index dd486001c7..33ad97d821 100644
--- a/apps/ios/cs.lproj/Localizable.strings
+++ b/apps/ios/cs.lproj/Localizable.strings
@@ -1258,7 +1258,8 @@ swipe action */
/* No comment provided by engineer. */
"Delete message?" = "Smazat zprávu?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "Smazat zprávy";
/* No comment provided by engineer. */
@@ -2193,7 +2194,7 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "Role člena se změní na \"%@\". Člen obdrží novou pozvánku.";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "Člen bude odstraněn ze skupiny - toto nelze vzít zpět!";
/* No comment provided by engineer. */
@@ -2728,13 +2729,13 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "Přenosový server chrání vaši IP adresu, ale může sledovat dobu trvání hovoru.";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "Odstranit";
/* No comment provided by engineer. */
"Remove member" = "Odstranit člena";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "Odebrat člena?";
/* No comment provided by engineer. */
diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings
index caf58399de..f305aca473 100644
--- a/apps/ios/de.lproj/Localizable.strings
+++ b/apps/ios/de.lproj/Localizable.strings
@@ -1733,7 +1733,8 @@ swipe action */
/* No comment provided by engineer. */
"Delete message?" = "Die Nachricht löschen?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "Nachrichten löschen";
/* No comment provided by engineer. */
@@ -3308,10 +3309,10 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "Die Mitgliederrolle wird auf \"%@\" geändert. Das Mitglied wird eine neue Einladung erhalten.";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from chat - this cannot be undone!" = "Das Mitglied wird aus dem Chat entfernt. Dies kann nicht rückgängig gemacht werden!";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "Das Mitglied wird aus der Gruppe entfernt. Dies kann nicht rückgängig gemacht werden!";
/* alert message */
@@ -4380,7 +4381,7 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "Relais-Server schützen Ihre IP-Adresse, aber sie können die Anrufdauer erfassen.";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "Entfernen";
/* No comment provided by engineer. */
@@ -4395,7 +4396,7 @@ swipe action */
/* No comment provided by engineer. */
"Remove member" = "Mitglied entfernen";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "Das Mitglied entfernen?";
/* No comment provided by engineer. */
diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings
index 5ce7ab6843..9ac7628abb 100644
--- a/apps/ios/es.lproj/Localizable.strings
+++ b/apps/ios/es.lproj/Localizable.strings
@@ -384,7 +384,7 @@ swipe action */
"accepted invitation" = "invitación aceptada";
/* rcv group event chat item */
-"accepted you" = "te ha aceptado";
+"accepted you" = "te ha admitido";
/* No comment provided by engineer. */
"Acknowledged" = "Confirmaciones";
@@ -1733,7 +1733,8 @@ swipe action */
/* No comment provided by engineer. */
"Delete message?" = "¿Eliminar mensaje?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "Activar";
/* No comment provided by engineer. */
@@ -3308,10 +3309,10 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "El rol del miembro cambiará a \"%@\" y recibirá una invitación nueva.";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from chat - this cannot be undone!" = "El miembro será eliminado del chat. ¡No puede deshacerse!";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "El miembro será expulsado del grupo. ¡No puede deshacerse!";
/* alert message */
@@ -3654,7 +3655,7 @@ snd error text */
"No device token!" = "¡Sin dispositivo token!";
/* item status description */
-"No direct connection yet, message is forwarded by admin." = "Aún no hay conexión directa con este miembro, el mensaje es reenviado por el administrador.";
+"No direct connection yet, message is forwarded by admin." = "Aún no hay conexión directa, los mensajes son reenviados por el administrador.";
/* No comment provided by engineer. */
"no e2e encryption" = "sin cifrar";
@@ -4380,7 +4381,7 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "El servidor de retransmisión protege tu IP pero puede ver la duración de la llamada.";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "Eliminar";
/* No comment provided by engineer. */
@@ -4395,7 +4396,7 @@ swipe action */
/* No comment provided by engineer. */
"Remove member" = "Expulsar miembro";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "¿Expulsar miembro?";
/* No comment provided by engineer. */
@@ -6052,7 +6053,7 @@ report reason */
"You accepted connection" = "Has aceptado la conexión";
/* snd group event chat item */
-"you accepted this member" = "has aceptado al miembro";
+"you accepted this member" = "has admitido al miembro";
/* No comment provided by engineer. */
"You allow" = "Permites";
diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings
index 884be40cc1..ea3f9c4386 100644
--- a/apps/ios/fi.lproj/Localizable.strings
+++ b/apps/ios/fi.lproj/Localizable.strings
@@ -943,7 +943,8 @@ swipe action */
/* No comment provided by engineer. */
"Delete message?" = "Poista viesti?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "Poista viestit";
/* No comment provided by engineer. */
@@ -1869,7 +1870,7 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "Jäsenen rooli muutetaan muotoon \"%@\". Jäsen saa uuden kutsun.";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "Jäsen poistetaan ryhmästä - tätä ei voi perua!";
/* No comment provided by engineer. */
@@ -2398,13 +2399,13 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "Välityspalvelin suojaa IP-osoitteesi, mutta se voi tarkkailla puhelun kestoa.";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "Poista";
/* No comment provided by engineer. */
"Remove member" = "Poista jäsen";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "Poista jäsen?";
/* No comment provided by engineer. */
diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings
index dbfac375d1..2b2a1e98e5 100644
--- a/apps/ios/fr.lproj/Localizable.strings
+++ b/apps/ios/fr.lproj/Localizable.strings
@@ -1661,7 +1661,8 @@ swipe action */
/* No comment provided by engineer. */
"Delete message?" = "Supprimer le message ?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "Supprimer les messages";
/* No comment provided by engineer. */
@@ -3104,10 +3105,10 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "Le rôle du membre sera changé pour \"%@\". Ce membre recevra une nouvelle invitation.";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from chat - this cannot be undone!" = "Le membre sera retiré de la discussion - cela ne peut pas être annulé !";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "Ce membre sera retiré du groupe - impossible de revenir en arrière !";
/* No comment provided by engineer. */
@@ -4005,7 +4006,7 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "Le serveur relais protège votre adresse IP, mais il peut observer la durée de l'appel.";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "Supprimer";
/* No comment provided by engineer. */
@@ -4017,7 +4018,7 @@ swipe action */
/* No comment provided by engineer. */
"Remove member" = "Retirer le membre";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "Retirer ce membre ?";
/* No comment provided by engineer. */
diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings
index 451bdfc699..fc2f796bfd 100644
--- a/apps/ios/hu.lproj/Localizable.strings
+++ b/apps/ios/hu.lproj/Localizable.strings
@@ -41,10 +41,10 @@
"**Create group**: to create a new group." = "**Csoport létrehozása:** új csoport létrehozásához.";
/* No comment provided by engineer. */
-"**e2e encrypted** audio call" = "**e2e titkosított** hanghívás";
+"**e2e encrypted** audio call" = "**végpontok között titkosított** hanghívás";
/* No comment provided by engineer. */
-"**e2e encrypted** video call" = "**e2e titkosított** videóhívás";
+"**e2e encrypted** video call" = "**végpontok között titkosított** videóhívás";
/* No comment provided by engineer. */
"**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**Privátabb:** 20 percenként ellenőrzi az új üzeneteket. Az eszköztoken meg lesz osztva a SimpleX Chat kiszolgálóval, de az nem, hogy hány partnere vagy üzenete van.";
@@ -65,7 +65,7 @@
"**Scan / Paste link**: to connect via a link you received." = "**Hivatkozás beolvasása / beillesztése**: egy kapott hivatkozáson keresztüli kapcsolódáshoz.";
/* No comment provided by engineer. */
-"**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Figyelmeztetés:** Az azonnali push-értesítésekhez a kulcstartóban tárolt jelmondat megadása szükséges.";
+"**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Figyelmeztetés:** Az azonnali leküldéses értesítésekhez a kulcstartóban tárolt jelmondat megadása szükséges.";
/* No comment provided by engineer. */
"**Warning**: the archive will be removed." = "**Figyelmeztetés:** az archívum el lesz távolítva.";
@@ -119,10 +119,10 @@
"%@ is connected!" = "%@ kapcsolódott!";
/* No comment provided by engineer. */
-"%@ is not verified" = "%@ nincs hitelesítve";
+"%@ is not verified" = "%@ nincs ellenőrizve";
/* No comment provided by engineer. */
-"%@ is verified" = "%@ hitelesítve";
+"%@ is verified" = "%@ ellenőrizve";
/* No comment provided by engineer. */
"%@ server" = "%@ kiszolgáló";
@@ -194,7 +194,7 @@
"%lld %@" = "%lld %@";
/* No comment provided by engineer. */
-"%lld contact(s) selected" = "%lld partner kijelölve";
+"%lld contact(s) selected" = "%lld partner kiválasztva";
/* No comment provided by engineer. */
"%lld file(s) with total size of %@" = "%lld fájl, %@ összméretben";
@@ -507,7 +507,7 @@ swipe action */
"All data is kept private on your device." = "Az összes adat privát módon van tárolva az eszközén.";
/* No comment provided by engineer. */
-"All group members will remain connected." = "Az összes csoporttag kapcsolatban marad.";
+"All group members will remain connected." = "Az összes csoporttag továbbra is kapcsolatban marad.";
/* feature role */
"all members" = "összes tag";
@@ -534,10 +534,10 @@ swipe action */
"All servers" = "Összes kiszolgáló";
/* No comment provided by engineer. */
-"All your contacts will remain connected." = "Az összes partnerével kapcsolatban marad.";
+"All your contacts will remain connected." = "Az összes partnerével továbbra is kapcsolatban marad.";
/* No comment provided by engineer. */
-"All your contacts will remain connected. Profile update will be sent to your contacts." = "A partnereivel kapcsolatban marad. A profilfrissítés el lesz küldve a partnerei számára.";
+"All your contacts will remain connected. Profile update will be sent to your contacts." = "Az összes partnerével továbbra is kapcsolatban marad. A profilfrissítés el lesz küldve a partnerei számára.";
/* No comment provided by engineer. */
"All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Az összes partnere, -beszélgetése és -fájlja biztonságosan titkosítva lesz, majd töredékekre bontva feltöltődnek a beállított XFTP-továbbítókiszolgálókra.";
@@ -609,7 +609,7 @@ swipe action */
"Allow your contacts to irreversibly delete sent messages. (24 hours)" = "Az elküldött üzenetek végleges törlése engedélyezve van a partnerei számára. (24 óra)";
/* No comment provided by engineer. */
-"Allow your contacts to send disappearing messages." = "Az eltűnő üzenetek küldésének engedélyezése a partnerei számára.";
+"Allow your contacts to send disappearing messages." = "Az eltűnő üzenetek küldése engedélyezve van a partnerei számára.";
/* No comment provided by engineer. */
"Allow your contacts to send files and media." = "A fájlok és a médiatartalmak küldése engedélyezve van a partnerei számára.";
@@ -630,10 +630,10 @@ swipe action */
"always" = "mindig";
/* No comment provided by engineer. */
-"Always use private routing." = "Mindig használjon privát útválasztást.";
+"Always use private routing." = "Mindig legyen használva privát útválasztás.";
/* No comment provided by engineer. */
-"Always use relay" = "Mindig használjon továbbítókiszolgálót";
+"Always use relay" = "Mindig legyen használva továbbítókiszolgáló";
/* No comment provided by engineer. */
"An empty chat profile with the provided name is created, and the app opens as usual." = "Egy üres csevegési profil lesz létrehozva a megadott névvel, és az alkalmazás a szokásos módon megnyílik.";
@@ -735,7 +735,7 @@ swipe action */
"Audio and video calls" = "Hang- és videóhívások";
/* No comment provided by engineer. */
-"audio call (not e2e encrypted)" = "hanghívás (nem e2e titkosított)";
+"audio call (not e2e encrypted)" = "hanghívás (végpontok között NEM titkosított)";
/* chat feature */
"Audio/video calls" = "Hang- és videóhívások";
@@ -819,10 +819,10 @@ swipe action */
"Better user experience" = "Továbbfejlesztett felhasználói élmény";
/* No comment provided by engineer. */
-"Bio" = "Névjegy";
+"Bio" = "Életrajz";
/* alert title */
-"Bio too large" = "A névjegy túl hosszú";
+"Bio too large" = "Az életrajz túl hosszú";
/* No comment provided by engineer. */
"Black" = "Fekete";
@@ -913,7 +913,7 @@ marked deleted chat item preview text */
"call" = "hívás";
/* No comment provided by engineer. */
-"Call already ended!" = "A hívás már befejeződött!";
+"Call already ended!" = "A hívás már véget ért!";
/* call status */
"call error" = "híváshiba";
@@ -1120,7 +1120,7 @@ set passcode view */
"Chinese and Spanish interface" = "Kínai és spanyol kezelőfelület";
/* No comment provided by engineer. */
-"Choose _Migrate from another device_ on the new device and scan QR code." = "Válassza az _Átköltöztetés egy másik eszközről_ opciót az új eszközén és olvassa be a QR-kódot.";
+"Choose _Migrate from another device_ on the new device and scan QR code." = "Válassza az _Átköltöztetés egy másik eszközről_ beállítást az új eszközén és olvassa be a QR-kódot.";
/* No comment provided by engineer. */
"Choose file" = "Fájl kiválasztása";
@@ -1138,25 +1138,25 @@ set passcode view */
"Chunks uploaded" = "Feltöltött töredékek";
/* swipe action */
-"Clear" = "Kiürítés";
+"Clear" = "Ürítés";
/* No comment provided by engineer. */
-"Clear conversation" = "Üzenetek kiürítése";
+"Clear conversation" = "Üzenetek ürítése";
/* No comment provided by engineer. */
-"Clear conversation?" = "Kiüríti az üzeneteket?";
+"Clear conversation?" = "Üríti a beszélgetés üzeneteit?";
/* No comment provided by engineer. */
-"Clear group?" = "Kiüríti a csoportot?";
+"Clear group?" = "Üríti a csoport üzeneteit?";
/* No comment provided by engineer. */
-"Clear or delete group?" = "Csoport kiürítése vagy törlése?";
+"Clear or delete group?" = "Csoport ürítése vagy törlése?";
/* No comment provided by engineer. */
-"Clear private notes?" = "Kiüríti a privát jegyzeteket?";
+"Clear private notes?" = "Üríti a privát jegyzetek tartalmát?";
/* No comment provided by engineer. */
-"Clear verification" = "Hitelesítés törlése";
+"Clear verification" = "Ellenőrzés törlése";
/* No comment provided by engineer. */
"Color chats with the new themes." = "Csevegések színezése új témákkal.";
@@ -1273,7 +1273,7 @@ set passcode view */
"Connect via link" = "Kapcsolódás egy hivatkozáson keresztül";
/* new chat sheet title */
-"Connect via one-time link" = "Kapcsolódás egyszer használható meghívón keresztül";
+"Connect via one-time link" = "Kapcsolódás az egyszer használható meghívón keresztül";
/* new chat action */
"Connect with %@" = "Kapcsolódás a következővel: %@";
@@ -1291,7 +1291,7 @@ set passcode view */
"Connected servers" = "Kapcsolódott kiszolgálók";
/* No comment provided by engineer. */
-"Connected to desktop" = "Kapcsolódva a számítógéphez";
+"Connected to desktop" = "Társítva a számítógéppel";
/* No comment provided by engineer. */
"connecting" = "kapcsolódás";
@@ -1312,7 +1312,7 @@ set passcode view */
"connecting (introduction invitation)" = "kapcsolódás (bemutatkozó meghívó)";
/* call status */
-"connecting call" = "kapcsolódási hívás…";
+"connecting call" = "hívás kapcsolása…";
/* No comment provided by engineer. */
"Connecting server…" = "Kapcsolódás a kiszolgálóhoz…";
@@ -1324,7 +1324,7 @@ set passcode view */
"Connecting to contact, please wait or check later!" = "Kapcsolódás a partnerhez, várjon vagy ellenőrizze később!";
/* No comment provided by engineer. */
-"Connecting to desktop" = "Kapcsolódás a számítógéphez";
+"Connecting to desktop" = "Társítás számítógéppel";
/* No comment provided by engineer. */
"connecting…" = "kapcsolódás…";
@@ -1399,10 +1399,10 @@ set passcode view */
"contact disabled" = "partner letiltva";
/* No comment provided by engineer. */
-"contact has e2e encryption" = "a partner e2e titkosítással rendelkezik";
+"contact has e2e encryption" = "a partner végpontok közötti titkosítással rendelkezik";
/* No comment provided by engineer. */
-"contact has no e2e encryption" = "a partner nem rendelkezik e2e titkosítással";
+"contact has no e2e encryption" = "a partner nem rendelkezik végpontok közötti titkosítással";
/* notification */
"Contact hidden:" = "Rejtett név:";
@@ -1486,7 +1486,7 @@ set passcode view */
"Create list" = "Lista létrehozása";
/* No comment provided by engineer. */
-"Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" = "Új profil létrehozása a [számítógép-alkalmazásban](https://simplex.chat/downloads/). 💻";
+"Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" = "Új profil létrehozása a [számítógépes alkalmazásban](https://simplex.chat/downloads/). 💻";
/* No comment provided by engineer. */
"Create profile" = "Profil létrehozása";
@@ -1656,7 +1656,7 @@ swipe action */
"Delete after" = "Törlés ennyi idő után";
/* No comment provided by engineer. */
-"Delete all files" = "Az összes fájl törlése";
+"Delete all files" = "Összes fájl törlése";
/* No comment provided by engineer. */
"Delete and notify contact" = "Törlés, és a partner értesítése";
@@ -1733,7 +1733,8 @@ swipe action */
/* No comment provided by engineer. */
"Delete message?" = "Törli az üzenetet?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "Üzenetek törlése";
/* No comment provided by engineer. */
@@ -1815,7 +1816,7 @@ swipe action */
"Desktop address" = "Számítógép címe";
/* No comment provided by engineer. */
-"Desktop app version %@ is not compatible with this app." = "A számítógép-alkalmazás verziója (%@) nem kompatibilis ezzel az alkalmazással.";
+"Desktop app version %@ is not compatible with this app." = "A számítógépes alkalmazás verziója (%@) nem kompatibilis ezzel az alkalmazással.";
/* No comment provided by engineer. */
"Desktop devices" = "Számítógépek";
@@ -1935,7 +1936,7 @@ swipe action */
"Do not use credentials with proxy." = "Ne használja a hitelesítési adatokat proxyval.";
/* No comment provided by engineer. */
-"Do NOT use private routing." = "NE használjon privát útválasztást.";
+"Do NOT use private routing." = "NE legyen használva privát útválasztás.";
/* No comment provided by engineer. */
"Do NOT use SimpleX for emergency calls." = "NE használja a SimpleXet segélyhívásokhoz.";
@@ -1947,13 +1948,13 @@ swipe action */
"Don't create address" = "Ne hozzon létre címet";
/* No comment provided by engineer. */
-"Don't enable" = "Ne engedélyezze";
+"Don't enable" = "Nem engedélyezem";
/* No comment provided by engineer. */
"Don't miss important messages." = "Ne maradjon le a fontos üzenetekről.";
/* alert action */
-"Don't show again" = "Ne mutasd újra";
+"Don't show again" = "Ne jelenjen meg újra";
/* No comment provided by engineer. */
"Done" = "Kész";
@@ -2002,10 +2003,10 @@ chat item action */
"Duration" = "Időtartam";
/* No comment provided by engineer. */
-"e2e encrypted" = "e2e titkosított";
+"e2e encrypted" = "végpontok között titkosított";
/* No comment provided by engineer. */
-"E2E encrypted notifications." = "Végpontok közötti titkosított értesítések.";
+"E2E encrypted notifications." = "Végpontok között titkosított értesítések.";
/* chat item action */
"Edit" = "Szerkesztés";
@@ -2080,7 +2081,7 @@ chat item action */
"enabled for you" = "engedélyezve az Ön számára";
/* No comment provided by engineer. */
-"Encrypt" = "Titkosít";
+"Encrypt" = "Titkosítás";
/* No comment provided by engineer. */
"Encrypt database?" = "Titkosítja az adatbázist?";
@@ -2149,10 +2150,10 @@ chat item action */
"Encryption renegotiation in progress." = "A titkosítás újraegyeztetése folyamatban van.";
/* No comment provided by engineer. */
-"ended" = "befejeződött";
+"ended" = "hívás vége";
/* call status */
-"ended call %@" = "%@ hívása befejeződött";
+"ended call %@" = "%@ hívása véget ért";
/* No comment provided by engineer. */
"Enter correct passphrase." = "Adja meg a helyes jelmondatot.";
@@ -2431,7 +2432,7 @@ chat item action */
"Error uploading the archive" = "Hiba történt az archívum feltöltésekor";
/* No comment provided by engineer. */
-"Error verifying passphrase:" = "Hiba történt a jelmondat hitelesítésekor:";
+"Error verifying passphrase:" = "Hiba történt a jelmondat ellenőrzésekor:";
/* No comment provided by engineer. */
"Error: " = "Hiba: ";
@@ -2832,7 +2833,7 @@ snd error text */
"How SimpleX works" = "Hogyan működik a SimpleX";
/* No comment provided by engineer. */
-"How to" = "Hogyan";
+"How to" = "Útmutató";
/* No comment provided by engineer. */
"How to use it" = "Használati útmutató";
@@ -2856,7 +2857,7 @@ snd error text */
"If you enter your self-destruct passcode while opening the app:" = "Ha az alkalmazás megnyitásakor megadja az önmegsemmisítő jelkódot:";
/* No comment provided by engineer. */
-"If you need to use the chat now tap **Do it later** below (you will be offered to migrate the database when you restart the app)." = "Ha most kell használnia a csevegést, koppintson alább a **Befejezés később** lehetőségre (az alkalmazás újraindításakor fel lesz ajánlva az adatbázis átköltöztetése).";
+"If you need to use the chat now tap **Do it later** below (you will be offered to migrate the database when you restart the app)." = "Ha most kell használnia a csevegést, koppintson lentebb a **Befejezés később** beállításra (az alkalmazás újraindításakor fel lesz ajánlva az adatbázis átköltöztetése).";
/* No comment provided by engineer. */
"Ignore" = "Mellőzés";
@@ -2979,7 +2980,7 @@ snd error text */
"Instant" = "Azonnali";
/* No comment provided by engineer. */
-"Instant push notifications will be hidden!\n" = "Az azonnali push-értesítések el lesznek rejtve!\n";
+"Instant push notifications will be hidden!\n" = "Az azonnali leküldéses értesítések el lesznek rejtve!\n";
/* No comment provided by engineer. */
"Interface" = "Kezelőfelület";
@@ -3072,10 +3073,10 @@ snd error text */
"invited via your group link" = "meghíva a saját csoporthivatkozásán keresztül";
/* No comment provided by engineer. */
-"iOS Keychain is used to securely store passphrase - it allows receiving push notifications." = "Az iOS kulcstartó a jelmondat biztonságos tárolására szolgál – lehetővé teszi a push-értesítések fogadását.";
+"iOS Keychain is used to securely store passphrase - it allows receiving push notifications." = "Az iOS kulcstartó a jelmondat biztonságos tárolására szolgál – lehetővé teszi a leküldéses értesítések fogadását.";
/* No comment provided by engineer. */
-"iOS Keychain will be used to securely store passphrase after you restart the app or change passphrase - it will allow receiving push notifications." = "Az iOS kulcstartó biztonságosan fogja tárolni a jelmondatot az alkalmazás újraindítása, vagy a jelmondat módosítása után – lehetővé teszi a push-értesítések fogadását.";
+"iOS Keychain will be used to securely store passphrase after you restart the app or change passphrase - it will allow receiving push notifications." = "Az iOS kulcstartó biztonságosan fogja tárolni a jelmondatot az alkalmazás újraindítása, vagy a jelmondat módosítása után – lehetővé teszi a leküldéses értesítések fogadását.";
/* No comment provided by engineer. */
"IP address" = "IP-cím";
@@ -3141,7 +3142,7 @@ snd error text */
"Keep conversation" = "Beszélgetés megtartása";
/* No comment provided by engineer. */
-"Keep the app open to use it from desktop" = "A számítógépről való használathoz tartsd nyitva az alkalmazást";
+"Keep the app open to use it from desktop" = "Alkalmazás megnyitva tartása a számítógépről való használathoz";
/* alert title */
"Keep unused invitation?" = "Megtartja a fel nem használt meghívót?";
@@ -3195,7 +3196,7 @@ snd error text */
"Limitations" = "Korlátozások";
/* No comment provided by engineer. */
-"Link mobile and desktop apps! 🔗" = "Társítsa össze a hordozható eszköz- és számítógépes alkalmazásokat! 🔗";
+"Link mobile and desktop apps! 🔗" = "Társítsa össze a hordozható eszköz- és a számítógépes alkalmazásokat! 🔗";
/* No comment provided by engineer. */
"Linked desktop options" = "Társított számítógép beállítások";
@@ -3252,7 +3253,7 @@ snd error text */
"Mark read" = "Megjelölés olvasottként";
/* No comment provided by engineer. */
-"Mark verified" = "Hitelesítés";
+"Mark verified" = "Megjelölés ellenőrzöttként";
/* No comment provided by engineer. */
"Markdown in messages" = "Markdown az üzenetekben";
@@ -3261,7 +3262,7 @@ snd error text */
"marked deleted" = "törlésre jelölve";
/* No comment provided by engineer. */
-"Max 30 seconds, received instantly." = "Max. 30 másodperc, azonnal érkezett.";
+"Max 30 seconds, received instantly." = "Legfeljebb 30 másodperc, azonnal megérkezik.";
/* No comment provided by engineer. */
"Media & file servers" = "Fájl- és médiakiszolgálók";
@@ -3308,10 +3309,10 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "A tag szerepköre a következőre fog módosulni: „%@”. A tag új meghívást fog kapni.";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from chat - this cannot be undone!" = "A tag el lesz távolítva a csevegésből – ez a művelet nem vonható vissza!";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "A tag el lesz távolítva a csoportból – ez a művelet nem vonható vissza!";
/* alert message */
@@ -3360,7 +3361,7 @@ snd error text */
"Message delivery warning" = "Üzenetkézbesítési figyelmeztetés";
/* No comment provided by engineer. */
-"Message draft" = "Üzenetvázlat";
+"Message draft" = "Piszkozatok";
/* item status text */
"Message forwarded" = "Továbbított üzenet";
@@ -3432,7 +3433,7 @@ snd error text */
"Messages sent" = "Elküldött üzenetek";
/* alert message */
-"Messages were deleted after you selected them." = "Az üzeneteket törölték miután kijelölte őket.";
+"Messages were deleted after you selected them." = "Az üzeneteket törölték miután kiváasztotta őket.";
/* No comment provided by engineer. */
"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Az üzenetek, a fájlok és a hívások **végpontok közötti titkosítással**, kompromittálás előtti és utáni titkosságvédelemmel, illetve letagadhatósággal vannak védve.";
@@ -3468,7 +3469,7 @@ snd error text */
"Migration error:" = "Átköltöztetési hiba:";
/* No comment provided by engineer. */
-"Migration failed. Tap **Skip** below to continue using the current database. Please report the issue to the app developers via chat or email [chat@simplex.chat](mailto:chat@simplex.chat)." = "Sikertelen átköltöztetés. Koppintson a **Kihagyás** lehetőségre a jelenlegi adatbázis használatának folytatásához. Jelentse a problémát az alkalmazás fejlesztőinek csevegésben vagy e-mailben [chat@simplex.chat](mailto:chat@simplex.chat).";
+"Migration failed. Tap **Skip** below to continue using the current database. Please report the issue to the app developers via chat or email [chat@simplex.chat](mailto:chat@simplex.chat)." = "Sikertelen átköltöztetés. Koppintson a **Kihagyás** beállításra a jelenlegi adatbázis használatának folytatásához. Jelentse a problémát az alkalmazás fejlesztőinek csevegésben vagy e-mailben [chat@simplex.chat](mailto:chat@simplex.chat).";
/* No comment provided by engineer. */
"Migration is completed" = "Az átköltöztetés befejeződött";
@@ -3573,10 +3574,10 @@ snd error text */
"New contact request" = "Új partneri kapcsolatkérés";
/* notification */
-"New contact:" = "Új kapcsolat:";
+"New contact:" = "Új partner:";
/* No comment provided by engineer. */
-"New desktop app!" = "Új számítógép-alkalmazás!";
+"New desktop app!" = "Új számítógépes alkalmazás!";
/* No comment provided by engineer. */
"New display name" = "Új megjelenítendő név";
@@ -3642,7 +3643,7 @@ snd error text */
"No chats with members" = "Nincsenek csevegések a tagokkal";
/* No comment provided by engineer. */
-"No contacts selected" = "Nincs partner kijelölve";
+"No contacts selected" = "Nincs partner kiválasztva";
/* No comment provided by engineer. */
"No contacts to add" = "Nincs hozzáadandó partner";
@@ -3657,7 +3658,7 @@ snd error text */
"No direct connection yet, message is forwarded by admin." = "Még nincs közvetlen kapcsolat, az üzenetet az adminisztrátor továbbítja.";
/* No comment provided by engineer. */
-"no e2e encryption" = "nincs e2e titkosítás";
+"no e2e encryption" = "nincs végpontok közötti titkosítás";
/* No comment provided by engineer. */
"No filtered chats" = "Nincsenek szűrt csevegések";
@@ -3696,7 +3697,7 @@ snd error text */
"No private routing session" = "Nincs privát útválasztási munkamenet";
/* No comment provided by engineer. */
-"No push server" = "Helyi";
+"No push server" = "Nincs kiszolgáló a leküldéses értesítésekhez";
/* No comment provided by engineer. */
"No received or sent files" = "Nincsenek fogadott vagy küldött fájlok";
@@ -3714,7 +3715,7 @@ snd error text */
"No servers to send files." = "Nincsenek fájlküldési kiszolgálók.";
/* No comment provided by engineer. */
-"no subscription" = "nincs előfizetés";
+"no subscription" = "nincs feliratkozás";
/* copied message info in history */
"no text" = "nincs szöveg";
@@ -3738,7 +3739,7 @@ snd error text */
"Notes" = "Jegyzetek";
/* No comment provided by engineer. */
-"Nothing selected" = "Nincs semmi kijelölve";
+"Nothing selected" = "Nincs semmi kiválasztva";
/* alert title */
"Nothing to forward!" = "Nincs mit továbbítani!";
@@ -3833,37 +3834,37 @@ new chat action */
"Only you can add message reactions." = "Csak Ön adhat hozzá reakciókat az üzenetekhez.";
/* No comment provided by engineer. */
-"Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)" = "Véglegesen csak Ön törölhet üzeneteket (partnere csak törlésre jelölheti meg őket ). (24 óra)";
+"Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)" = "Csak Ön törölheti véglegesen az üzeneteket (partnere csak törlésre jelölheti meg azokat ). (24 óra)";
/* No comment provided by engineer. */
-"Only you can make calls." = "Csak Ön tud hívásokat indítani.";
+"Only you can make calls." = "Csak Ön kezdeményezhet hívásokat.";
/* No comment provided by engineer. */
-"Only you can send disappearing messages." = "Csak Ön tud eltűnő üzeneteket küldeni.";
+"Only you can send disappearing messages." = "Csak Ön küldhet eltűnő üzeneteket.";
/* No comment provided by engineer. */
"Only you can send files and media." = "Csak Ön küldhet fájlokat és médiatartalmakat.";
/* No comment provided by engineer. */
-"Only you can send voice messages." = "Csak Ön tud hangüzeneteket küldeni.";
+"Only you can send voice messages." = "Csak Ön küldhet hangüzeneteket.";
/* No comment provided by engineer. */
"Only your contact can add message reactions." = "Csak a partnere adhat hozzá reakciókat az üzenetekhez.";
/* No comment provided by engineer. */
-"Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours)" = "Csak a partnere tudja az üzeneteket véglegesen törölni (Ön csak törlésre jelölheti meg azokat). (24 óra)";
+"Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours)" = "Csak a partnere törölheti véglegesen az üzeneteket (Ön csak törlésre jelölheti meg azokat). (24 óra)";
/* No comment provided by engineer. */
-"Only your contact can make calls." = "Csak a partnere tud hívást indítani.";
+"Only your contact can make calls." = "Csak a partnere kezdeményezhet hívásokat.";
/* No comment provided by engineer. */
-"Only your contact can send disappearing messages." = "Csak a partnere tud eltűnő üzeneteket küldeni.";
+"Only your contact can send disappearing messages." = "Csak a partnere küldhet eltűnő üzeneteket.";
/* No comment provided by engineer. */
"Only your contact can send files and media." = "Csak a partnere küldhet fájlokat és médiatartalmakat.";
/* No comment provided by engineer. */
-"Only your contact can send voice messages." = "Csak a partnere tud hangüzeneteket küldeni.";
+"Only your contact can send voice messages." = "Csak a partnere küldhet hangüzeneteket.";
/* alert action */
"Open" = "Megnyitás";
@@ -4070,7 +4071,7 @@ new chat action */
"Please report it to the developers." = "Jelentse a fejlesztőknek.";
/* No comment provided by engineer. */
-"Please restart the app and migrate the database to enable push notifications." = "Indítsa újra az alkalmazást az adatbázis-átköltöztetéséhez szükséges push-értesítések engedélyezéséhez.";
+"Please restart the app and migrate the database to enable push notifications." = "Indítsa újra az alkalmazást az adatbázis-átköltöztetéséhez szükséges leküldéses értesítések engedélyezéséhez.";
/* No comment provided by engineer. */
"Please store passphrase securely, you will NOT be able to access chat if you lose it." = "Tárolja el biztonságosan jelmondát, mert ha elveszti azt, akkor NEM férhet hozzá a csevegéshez.";
@@ -4181,7 +4182,7 @@ new chat action */
"Prohibit messages reactions." = "A reakciók hozzáadása az üzenetekhez le van tiltva.";
/* No comment provided by engineer. */
-"Prohibit reporting messages to moderators." = "Az üzenetek a moderátorok felé történő jelentésének megtiltása.";
+"Prohibit reporting messages to moderators." = "Az üzenetek jelentése a moderátorok felé le van tiltva.";
/* No comment provided by engineer. */
"Prohibit sending direct messages to members." = "A közvetlen üzenetek küldése a tagok között le van tiltva.";
@@ -4229,10 +4230,10 @@ new chat action */
"Proxy requires password" = "A proxy jelszót igényel";
/* No comment provided by engineer. */
-"Push notifications" = "Push-értesítések";
+"Push notifications" = "Leküldéses értesítések";
/* No comment provided by engineer. */
-"Push server" = "Push-kiszolgáló";
+"Push server" = "Leküldéses értesítéskiszolgáló";
/* chat item text */
"quantum resistant e2e encryption" = "végpontok közötti kvantumbiztos titkosítás";
@@ -4241,13 +4242,13 @@ new chat action */
"Quantum resistant encryption" = "Kvantumbiztos titkosítás";
/* No comment provided by engineer. */
-"Rate the app" = "Értékelje az alkalmazást";
+"Rate the app" = "Alkalmazás értékelése";
/* No comment provided by engineer. */
"Reachable chat toolbar" = "Könnyen elérhető csevegési eszköztár";
/* chat item menu */
-"React…" = "Reagálj…";
+"React…" = "Reagálás…";
/* swipe action */
"Read" = "Olvasott";
@@ -4274,7 +4275,7 @@ new chat action */
"Receive errors" = "Üzenetfogadási hibák";
/* No comment provided by engineer. */
-"received answer…" = "válasz fogadása…";
+"received answer…" = "válasz érkezett…";
/* No comment provided by engineer. */
"Received at" = "Fogadva";
@@ -4283,7 +4284,7 @@ new chat action */
"Received at: %@" = "Fogadva: %@";
/* No comment provided by engineer. */
-"received confirmation…" = "visszaigazolás fogadása…";
+"received confirmation…" = "visszaigazolás érkezett…";
/* message info title */
"Received message" = "Fogadott üzenetbuborék színe";
@@ -4360,7 +4361,7 @@ swipe action */
"Reject" = "Elutasítás";
/* No comment provided by engineer. */
-"Reject (sender NOT notified)" = "Elutasítás (a kérés küldője NEM fog értesítést kapni)";
+"Reject (sender NOT notified)" = "Elutasítás (a kérés küldője NEM lesz értesítve)";
/* alert title */
"Reject contact request" = "Partneri kapcsolatkérés elutasítása";
@@ -4380,7 +4381,7 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "A továbbítókiszolgáló megvédi az IP-címét, de megfigyelheti a hívás időtartamát.";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "Eltávolítás";
/* No comment provided by engineer. */
@@ -4395,7 +4396,7 @@ swipe action */
/* No comment provided by engineer. */
"Remove member" = "Eltávolítás";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "Eltávolítja a tagot?";
/* No comment provided by engineer. */
@@ -4501,7 +4502,7 @@ swipe action */
"Reset all hints" = "Tippek visszaállítása";
/* No comment provided by engineer. */
-"Reset all statistics" = "Az összes statisztika visszaállítása";
+"Reset all statistics" = "Összes statisztika visszaállítása";
/* No comment provided by engineer. */
"Reset all statistics?" = "Visszaállítja az összes statisztikát?";
@@ -4712,7 +4713,7 @@ chat item action */
"Secured" = "Biztosítva";
/* No comment provided by engineer. */
-"Security assessment" = "Biztonsági kiértékelés";
+"Security assessment" = "Biztonsági felmérés";
/* No comment provided by engineer. */
"Security code" = "Biztonsági kód";
@@ -4721,16 +4722,16 @@ chat item action */
"security code changed" = "biztonsági kódja módosult";
/* chat item action */
-"Select" = "Kijelölés";
+"Select" = "Kiválasztás";
/* No comment provided by engineer. */
-"Select chat profile" = "Csevegési profil kijelölése";
+"Select chat profile" = "Csevegési profil kiválasztása";
/* No comment provided by engineer. */
-"Selected %lld" = "%lld kijelölve";
+"Selected %lld" = "%lld kiválasztva";
/* No comment provided by engineer. */
-"Selected chat preferences prohibit this message." = "A kijelölt csevegési beállítások tiltják ezt az üzenetet.";
+"Selected chat preferences prohibit this message." = "A kiválasztott csevegési beállítások tiltják ezt az üzenetet.";
/* No comment provided by engineer. */
"Self-destruct" = "Önmegsemmisítés";
@@ -4823,13 +4824,13 @@ chat item action */
"Sending file will be stopped." = "A fájl küldése le fog állni.";
/* No comment provided by engineer. */
-"Sending receipts is disabled for %lld contacts" = "A kézbesítési jelentések le vannak tiltva %lld partnernél";
+"Sending receipts is disabled for %lld contacts" = "A kézbesítési jelentések le vannak tiltva %lld partner számára";
/* No comment provided by engineer. */
"Sending receipts is disabled for %lld groups" = "A kézbesítési jelentések le vannak tiltva %lld csoportban";
/* No comment provided by engineer. */
-"Sending receipts is enabled for %lld contacts" = "A kézbesítési jelentések engedélyezve vannak %lld partnernél";
+"Sending receipts is enabled for %lld contacts" = "A kézbesítési jelentések engedélyezve vannak %lld partner számára";
/* No comment provided by engineer. */
"Sending receipts is enabled for %lld groups" = "A kézbesítési jelentések engedélyezve vannak %lld csoportban";
@@ -4919,7 +4920,7 @@ chat item action */
"Servers statistics will be reset - this cannot be undone!" = "A kiszolgálók statisztikái visszaállnak – ez a művelet nem vonható vissza!";
/* No comment provided by engineer. */
-"Session code" = "Munkamenet kód";
+"Session code" = "Munkamenet kódja";
/* No comment provided by engineer. */
"Set 1 day" = "Beállítva 1 nap";
@@ -4961,7 +4962,7 @@ chat item action */
"Set passphrase to export" = "Jelmondat beállítása az exportáláshoz";
/* No comment provided by engineer. */
-"Set profile bio and welcome message." = "Névjegy és üdvözlőüzenet beállítása a profilokhoz.";
+"Set profile bio and welcome message." = "Életrajz és üdvözlőüzenet beállítása a profilokhoz.";
/* No comment provided by engineer. */
"Set the message shown to new members!" = "Megjelenítendő üzenet beállítása az új tagok számára!";
@@ -5079,7 +5080,7 @@ chat item action */
"SimpleX address or 1-time link?" = "SimpleX-cím vagy egyszer használható meghívó?";
/* alert title */
-"SimpleX address settings" = "Beállítások automatikus elfogadása";
+"SimpleX address settings" = "SimpleX-címbeállítások";
/* simplex link type */
"SimpleX channel link" = "SimpleX-csatornahivatkozás";
@@ -5142,7 +5143,7 @@ chat item action */
"Skipped messages" = "Kihagyott üzenetek";
/* No comment provided by engineer. */
-"Small groups (max 20)" = "Kis csoportok (max. 20 tag)";
+"Small groups (max 20)" = "Kis csoportok (legfeljebb 20 tag)";
/* No comment provided by engineer. */
"SMP server" = "SMP-kiszolgáló";
@@ -5182,7 +5183,7 @@ report reason */
"standard end-to-end encryption" = "szabványos végpontok közötti titkosítás";
/* No comment provided by engineer. */
-"Start chat" = "Csevegés indítása";
+"Start chat" = "Csevegés elindítása";
/* No comment provided by engineer. */
"Start chat?" = "Elindítja a csevegést?";
@@ -5194,7 +5195,7 @@ report reason */
"Starting from %@." = "Statisztikagyűjtés kezdete: %@.";
/* No comment provided by engineer. */
-"starting…" = "indítás…";
+"starting…" = "hívás indítása…";
/* No comment provided by engineer. */
"Statistics" = "Statisztikák";
@@ -5425,10 +5426,10 @@ report reason */
"The second preset operator in the app!" = "A második előre beállított üzemeltető az alkalmazásban!";
/* No comment provided by engineer. */
-"The second tick we missed! ✅" = "A második jelölés, amit kihagytunk! ✅";
+"The second tick we missed! ✅" = "A második pipa, ami már nagyon hiányzott! ✅";
/* alert message */
-"The sender will NOT be notified" = "A kérés küldője NEM fog értesítést kapni";
+"The sender will NOT be notified" = "A kérés küldője NEM lesz értesítve";
/* No comment provided by engineer. */
"The servers for new connections of your current chat profile **%@**." = "A jelenlegi **%@** nevű csevegési profiljához tartozó új kapcsolatok kiszolgálói.";
@@ -5458,10 +5459,10 @@ report reason */
"This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." = "Ez a művelet nem vonható vissza – az összes fogadott és küldött fájl a médiatartalmakkal együtt törölve lesznek. Az alacsony felbontású képek viszont megmaradnak.";
/* No comment provided by engineer. */
-"This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." = "Ez a művelet nem vonható vissza – a kijelöltnél korábban küldött és fogadott üzenetek törölve lesznek. Ez több percet is igénybe vehet.";
+"This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." = "Ez a művelet nem vonható vissza – a kiválasztott üzenettől korábban küldött és fogadott üzenetek törölve lesznek. Ez több percet is igénybe vehet.";
/* alert message */
-"This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted." = "Ez a művelet nem vonható vissza – a kijelölt üzenettől korábban küldött és fogadott üzenetek törölve lesznek a csevegésből.";
+"This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted." = "Ez a művelet nem vonható vissza – a kiválasztott üzenettől korábban küldött és fogadott üzenetek törölve lesznek a csevegésből.";
/* No comment provided by engineer. */
"This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Ez a művelet nem vonható vissza – profiljai, partnerei, üzenetei és fájljai véglegesen törölve lesznek.";
@@ -5557,7 +5558,7 @@ report reason */
"To send commands you must be connected." = "A parancsok küldéséhez kapcsolódva kell lennie.";
/* No comment provided by engineer. */
-"To support instant push notifications the chat database has to be migrated." = "Az azonnali push-értesítések támogatásához a csevegési adatbázis átköltöztetése szükséges.";
+"To support instant push notifications the chat database has to be migrated." = "Az azonnali leküldéses értesítések támogatásához a csevegési adatbázis átköltöztetése szükséges.";
/* alert message */
"To use another profile after connection attempt, delete the chat and use the link again." = "Másik profil használatához a kapcsolatfelvételi kísérlet után törölje a csevegést, és használja újra a hivatkozást.";
@@ -5566,7 +5567,7 @@ report reason */
"To use the servers of **%@**, accept conditions of use." = "A(z) **%@** kiszolgálóinak használatához fogadja el a használati feltételeket.";
/* No comment provided by engineer. */
-"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "A végpontok közötti titkosítás hitelesítéséhez hasonlítsa össze (vagy olvassa be a QR-kódot) a partnere eszközén lévő kóddal.";
+"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "A végpontok közötti titkosítás ellenőrzéséhez hasonlítsa össze (vagy olvassa be a QR-kódot) a partnere eszközén lévő kóddal.";
/* No comment provided by engineer. */
"Toggle chat list:" = "Csevegési lista ki/be:";
@@ -5701,7 +5702,7 @@ report reason */
"Update" = "Frissítés";
/* No comment provided by engineer. */
-"Update database passphrase" = "Az adatbázis jelmondatának módosítása";
+"Update database passphrase" = "Adatbázis jelmondatának módosítása";
/* No comment provided by engineer. */
"Update network settings?" = "Módosítja a hálózati beállításokat?";
@@ -5830,7 +5831,7 @@ report reason */
"Use web port" = "Webport használata";
/* No comment provided by engineer. */
-"User selection" = "Felhasználó kijelölése";
+"User selection" = "Felhasználó kiválasztása";
/* No comment provided by engineer. */
"Username" = "Felhasználónév";
@@ -5845,25 +5846,25 @@ report reason */
"v%@ (%@)" = "v%@ (%@)";
/* No comment provided by engineer. */
-"Verify code with desktop" = "Kód hitelesítése a számítógépen";
+"Verify code with desktop" = "Kód ellenőrzése a számítógépen";
/* No comment provided by engineer. */
-"Verify connection" = "Kapcsolat hitelesítése";
+"Verify connection" = "Kapcsolat ellenőrzése";
/* No comment provided by engineer. */
-"Verify connection security" = "Biztonságos kapcsolat hitelesítése";
+"Verify connection security" = "Biztonságos kapcsolat ellenőrzése";
/* No comment provided by engineer. */
-"Verify connections" = "Kapcsolatok hitelesítése";
+"Verify connections" = "Kapcsolatok ellenőrzése";
/* No comment provided by engineer. */
-"Verify database passphrase" = "Az adatbázis jelmondatának hitelesítése";
+"Verify database passphrase" = "Adatbázis jelmondatának ellenőrzése";
/* No comment provided by engineer. */
-"Verify passphrase" = "Jelmondat hitelesítése";
+"Verify passphrase" = "Jelmondat ellenőrzése";
/* No comment provided by engineer. */
-"Verify security code" = "Biztonsági kód hitelesítése";
+"Verify security code" = "Biztonsági kód ellenőrzése";
/* No comment provided by engineer. */
"Via browser" = "Böngészőn keresztül";
@@ -5890,7 +5891,7 @@ report reason */
"Video call" = "Videóhívás";
/* No comment provided by engineer. */
-"video call (not e2e encrypted)" = "videóhívás (nem e2e titkosított)";
+"video call (not e2e encrypted)" = "videóhívás (végpontok között NEM titkosított)";
/* No comment provided by engineer. */
"Video will be received when your contact completes uploading it." = "A videó akkor érkezik meg, amikor a küldője befejezte annak feltöltését.";
@@ -6091,7 +6092,7 @@ report reason */
"You are invited to group" = "Ön meghívást kapott a csoportba";
/* subscription status explanation */
-"You are not connected to the server used to receive messages from this connection (no subscription)." = "Ön nem kapcsolódott ahhoz a kiszolgálóhoz, amely az adott partnerétől érkező üzenetek fogadására szolgál (nincs előfizetés).";
+"You are not connected to the server used to receive messages from this connection (no subscription)." = "Ön nem kapcsolódott ahhoz a kiszolgálóhoz, amely az adott partnerétől érkező üzenetek fogadására szolgál (nincs feliratkozás).";
/* No comment provided by engineer. */
"You are not connected to these servers. Private routing is used to deliver messages to them." = "Ön nem kapcsolódik ezekhez a kiszolgálókhoz. A privát útválasztás az üzenetek kézbesítésére szolgál.";
@@ -6148,7 +6149,7 @@ report reason */
"You can share this address with your contacts to let them connect with **%@**." = "Megoszthatja ezt a SimpleX-címet a partnereivel, hogy kapcsolatba léphessenek vele: **%@**.";
/* No comment provided by engineer. */
-"You can start chat via app Settings / Database or by restarting the app" = "A csevegést az alkalmazás „Beállítások / Adatbázis” menüben vagy az alkalmazás újraindításával indíthatja el";
+"You can start chat via app Settings / Database or by restarting the app" = "A csevegés elindítható az alkalmazás „Beállítások / Adatbázis” menüjében vagy az alkalmazás újraindításával";
/* No comment provided by engineer. */
"You can still view conversation with %@ in the list of chats." = "A(z) %@ nevű partnerével folytatott beszélgetéseit továbbra is megtekintheti a csevegések listájában.";
@@ -6181,7 +6182,7 @@ report reason */
"you changed role of %@ to %@" = "Ön a következőre módosította %1$@ szerepkörét: „%2$@”";
/* No comment provided by engineer. */
-"You could not be verified; please try again." = "Nem sikerült hitelesíteni; próbálja meg újra.";
+"You could not be verified; please try again." = "Nem sikerült ellenőrizni; próbálja meg újra.";
/* No comment provided by engineer. */
"You decide who can connect." = "Ön dönti el, hogy kivel beszélget.";
@@ -6262,10 +6263,10 @@ report reason */
"You will still receive calls and notifications from muted profiles when they are active." = "Továbbra is kap hívásokat és értesítéseket a némított profiloktól, ha azok aktívak.";
/* No comment provided by engineer. */
-"You will stop receiving messages from this chat. Chat history will be preserved." = "Ön nem fog több üzenetet kapni ebből a csevegésből, de a csevegés előzményei megmaradnak.";
+"You will stop receiving messages from this chat. Chat history will be preserved." = "Nem fog több üzenetet kapni ebből a csevegésből, de a csevegés előzményei megmaradnak.";
/* No comment provided by engineer. */
-"You will stop receiving messages from this group. Chat history will be preserved." = "Ettől a csoporttól nem fog értesítéseket kapni. A csevegési előzmények megmaradnak.";
+"You will stop receiving messages from this group. Chat history will be preserved." = "Nem fog több üzenetet kapni ebből a csoportból, de a csevegés előzményei megmaradnak.";
/* No comment provided by engineer. */
"You won't lose your contacts if you later delete your address." = "Nem veszíti el a partnereit, ha később törli a címét.";
@@ -6307,13 +6308,13 @@ report reason */
"Your contact" = "Partner";
/* No comment provided by engineer. */
-"Your contact sent a file that is larger than currently supported maximum size (%@)." = "A partnere a jelenleg megengedett maximális méretű (%@) fájlnál nagyobbat küldött.";
+"Your contact sent a file that is larger than currently supported maximum size (%@)." = "A partnere a jelenleg támogatott legnagyobb (%@) fájlméretnél nagyobbat küldött.";
/* No comment provided by engineer. */
"Your contacts can allow full message deletion." = "A partnerei engedélyezhetik a teljes üzenet törlését.";
/* No comment provided by engineer. */
-"Your contacts will remain connected." = "A partnerei továbbra is kapcsolódva maradnak.";
+"Your contacts will remain connected." = "A partnereivel továbbra is kapcsolatban marad.";
/* No comment provided by engineer. */
"Your credentials may be sent unencrypted." = "A hitelesítési adatai titkosítatlanul is elküldhetők.";
diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings
index 511a6835e5..9e2a27e618 100644
--- a/apps/ios/it.lproj/Localizable.strings
+++ b/apps/ios/it.lproj/Localizable.strings
@@ -1733,7 +1733,8 @@ swipe action */
/* No comment provided by engineer. */
"Delete message?" = "Eliminare il messaggio?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "Elimina messaggi";
/* No comment provided by engineer. */
@@ -3308,10 +3309,10 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "Il ruolo del membro verrà cambiato in \"%@\". Il membro riceverà un invito nuovo.";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from chat - this cannot be undone!" = "Il membro verrà rimosso dalla chat, non è reversibile!";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "Il membro verrà rimosso dal gruppo, non è reversibile!";
/* alert message */
@@ -3899,7 +3900,7 @@ new chat action */
"Open new chat" = "Apri una chat nuova";
/* new chat action */
-"Open new group" = "Apri un gruppo nuovo";
+"Open new group" = "Apri il nuovo gruppo";
/* No comment provided by engineer. */
"Open Settings" = "Apri le impostazioni";
@@ -4380,7 +4381,7 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "Il server relay protegge il tuo indirizzo IP, ma può osservare la durata della chiamata.";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "Rimuovi";
/* No comment provided by engineer. */
@@ -4395,7 +4396,7 @@ swipe action */
/* No comment provided by engineer. */
"Remove member" = "Rimuovi membro";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "Rimuovere il membro?";
/* No comment provided by engineer. */
diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings
index d4510af72f..480eb39d36 100644
--- a/apps/ios/ja.lproj/Localizable.strings
+++ b/apps/ios/ja.lproj/Localizable.strings
@@ -374,9 +374,18 @@ swipe action */
/* No comment provided by engineer. */
"Acknowledged" = "了承済み";
+/* No comment provided by engineer. */
+"Active connections" = "アクティブな接続";
+
/* No comment provided by engineer. */
"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "プロフィールにアドレスを追加し、連絡先があなたのアドレスを他の人と共有できるようにします。プロフィールの更新は連絡先に送信されます。";
+/* No comment provided by engineer. */
+"Add friends" = "友達を追加";
+
+/* No comment provided by engineer. */
+"Add list" = "リストを追加";
+
/* No comment provided by engineer. */
"Add profile" = "プロフィールを追加";
@@ -386,9 +395,15 @@ swipe action */
/* No comment provided by engineer. */
"Add servers by scanning QR codes." = "QRコードでサーバを追加する。";
+/* No comment provided by engineer. */
+"Add team members" = "チームメンバーを追加";
+
/* No comment provided by engineer. */
"Add to another device" = "別の端末に追加";
+/* No comment provided by engineer. */
+"Add to list" = "リストに追加";
+
/* No comment provided by engineer. */
"Add welcome message" = "ウェルカムメッセージを追加";
@@ -404,6 +419,9 @@ swipe action */
/* No comment provided by engineer. */
"Address change will be aborted. Old receiving address will be used." = "アドレス変更は中止されます。古い受信アドレスが使用されます。";
+/* No comment provided by engineer. */
+"Address settings" = "アドレス設定";
+
/* member role */
"admin" = "管理者";
@@ -422,6 +440,9 @@ swipe action */
/* chat item text */
"agreeing encryption…" = "暗号化に同意しています…";
+/* No comment provided by engineer. */
+"All" = "すべて";
+
/* No comment provided by engineer. */
"All app data is deleted." = "すべてのアプリデータが削除されます。";
@@ -434,6 +455,9 @@ swipe action */
/* No comment provided by engineer. */
"All group members will remain connected." = "グループ全員の接続が継続します。";
+/* No comment provided by engineer. */
+"All messages will be deleted - this cannot be undone!" = "すべてのメッセージが削除されます。この操作は元に戻せません!";
+
/* No comment provided by engineer. */
"All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "全てのメッセージが削除されます(※注意:元に戻せません!※)。削除されるのは片方あなたのメッセージのみ。";
@@ -455,9 +479,15 @@ swipe action */
/* No comment provided by engineer. */
"Allow calls only if your contact allows them." = "連絡先が通話を許可している場合のみ通話を許可する。";
+/* No comment provided by engineer. */
+"Allow calls?" = "通話を許可しますか?";
+
/* No comment provided by engineer. */
"Allow disappearing messages only if your contact allows it to you." = "連絡先が許可している場合のみ消えるメッセージを許可する。";
+/* No comment provided by engineer. */
+"Allow downgrade" = "ダウングレードを許可する";
+
/* No comment provided by engineer. */
"Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "送信相手も永久メッセージ削除を許可する時のみに許可する。(24時間)";
@@ -530,6 +560,9 @@ swipe action */
/* No comment provided by engineer. */
"An empty chat profile with the provided name is created, and the app opens as usual." = "指定された名前の空のチャット プロファイルが作成され、アプリが通常どおり開きます。";
+/* report reason */
+"Another reason" = "他の理由";
+
/* No comment provided by engineer. */
"Answer call" = "通話に応答";
@@ -569,9 +602,15 @@ swipe action */
/* No comment provided by engineer. */
"Apply to" = "に適用する";
+/* No comment provided by engineer. */
+"Archive" = "アーカイブ";
+
/* No comment provided by engineer. */
"Archive and upload" = "アーカイブとアップロード";
+/* No comment provided by engineer. */
+"Archived contacts" = "アーカイブされた連絡先";
+
/* No comment provided by engineer. */
"Attach" = "添付する";
@@ -668,6 +707,9 @@ swipe action */
/* No comment provided by engineer. */
"Calls" = "通話";
+/* alert title */
+"Can't change profile" = "プロフィールを変更できません";
+
/* No comment provided by engineer. */
"Can't invite contact!" = "連絡先を招待できません!";
@@ -679,12 +721,18 @@ alert button
new chat action */
"Cancel" = "中止";
+/* No comment provided by engineer. */
+"Cancel migration" = "移行を中止する";
+
/* feature offered item */
"cancelled %@" = "キャンセルされました %@";
/* No comment provided by engineer. */
"Cannot access keychain to save database password" = "データベースのパスワードを保存するためのキーチェーンにアクセスできません";
+/* No comment provided by engineer. */
+"Cannot forward message" = "メッセージを転送できません";
+
/* alert title */
"Cannot receive file" = "ファイル受信ができません";
@@ -734,6 +782,9 @@ set passcode view */
/* chat item text */
"changing address…" = "アドレスを変更しています…";
+/* No comment provided by engineer. */
+"Chat" = "チャット";
+
/* No comment provided by engineer. */
"Chat console" = "チャットのコンソール";
@@ -752,6 +803,9 @@ set passcode view */
/* No comment provided by engineer. */
"Chat is stopped" = "チャットが停止してます";
+/* No comment provided by engineer. */
+"Chat list" = "チャット一覧";
+
/* No comment provided by engineer. */
"Chat preferences" = "チャット設定";
@@ -764,6 +818,9 @@ set passcode view */
/* No comment provided by engineer. */
"Chats" = "チャット";
+/* No comment provided by engineer. */
+"Check messages every 20 min." = "20分おきにメッセージを確認する。";
+
/* alert title */
"Check server address and try again." = "サーバのアドレスを確認してから再度試してください。";
@@ -1168,7 +1225,8 @@ swipe action */
/* No comment provided by engineer. */
"Delete message?" = "メッセージを削除しますか?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "メッセージを削除";
/* No comment provided by engineer. */
@@ -2103,7 +2161,7 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "メンバーの役割が \"%@\" に変更されます。 メンバーは新たな招待を受け取ります。";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "メンバーをグループから除名する (※元に戻せません※)!";
/* No comment provided by engineer. */
@@ -2644,13 +2702,13 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "リレー サーバーは IP アドレスを保護しますが、通話時間は監視されます。";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "削除";
/* No comment provided by engineer. */
"Remove member" = "メンバーを除名する";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "メンバーを除名しますか?";
/* No comment provided by engineer. */
diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings
index 79e3da3b01..29f8bb5b3f 100644
--- a/apps/ios/nl.lproj/Localizable.strings
+++ b/apps/ios/nl.lproj/Localizable.strings
@@ -1688,7 +1688,8 @@ swipe action */
/* No comment provided by engineer. */
"Delete message?" = "Verwijder bericht?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "Verwijder berichten";
/* No comment provided by engineer. */
@@ -3197,10 +3198,10 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "De rol van lid wordt gewijzigd in \"%@\". Het lid ontvangt een nieuwe uitnodiging.";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from chat - this cannot be undone!" = "Lid wordt verwijderd uit de chat - dit kan niet ongedaan worden gemaakt!";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "Lid wordt uit de groep verwijderd, dit kan niet ongedaan worden gemaakt!";
/* alert message */
@@ -4218,7 +4219,7 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "Relay server beschermt uw IP-adres, maar kan de duur van het gesprek observeren.";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "Verwijderen";
/* No comment provided by engineer. */
@@ -4230,7 +4231,7 @@ swipe action */
/* No comment provided by engineer. */
"Remove member" = "Lid verwijderen";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "Lid verwijderen?";
/* No comment provided by engineer. */
diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings
index 34c79eeef4..9ef572364f 100644
--- a/apps/ios/pl.lproj/Localizable.strings
+++ b/apps/ios/pl.lproj/Localizable.strings
@@ -346,12 +346,21 @@ alert action
swipe action */
"Accept" = "Akceptuj";
+/* alert action */
+"Accept as member" = "Zaakceptuj jako członka";
+
+/* alert action */
+"Accept as observer" = "Zaakceptuj jako obserwatora";
+
/* No comment provided by engineer. */
"Accept conditions" = "Zaakceptuj warunki";
/* No comment provided by engineer. */
"Accept connection request?" = "Zaakceptować prośbę o połączenie?";
+/* alert title */
+"Accept contact request" = "Zaakceptuj prośby o kontakt";
+
/* notification body */
"Accept contact request from %@?" = "Zaakceptuj prośbę o kontakt od %@?";
@@ -359,6 +368,9 @@ swipe action */
swipe action */
"Accept incognito" = "Akceptuj incognito";
+/* alert title */
+"Accept member" = "Zaakceptuj członka";
+
/* call status */
"accepted call" = "zaakceptowane połączenie";
@@ -386,6 +398,9 @@ swipe action */
/* No comment provided by engineer. */
"Add list" = "Dodaj listę";
+/* placeholder for sending contact request */
+"Add message" = "Dodaj wiadomość";
+
/* No comment provided by engineer. */
"Add profile" = "Dodaj profil";
@@ -503,6 +518,9 @@ swipe action */
/* No comment provided by engineer. */
"All reports will be archived for you." = "Wszystkie raporty zostaną dla Ciebie zarchiwizowane.";
+/* No comment provided by engineer. */
+"All servers" = "Wszystkie serwery";
+
/* No comment provided by engineer. */
"All your contacts will remain connected." = "Wszystkie Twoje kontakty pozostaną połączone.";
@@ -527,6 +545,9 @@ swipe action */
/* No comment provided by engineer. */
"Allow downgrade" = "Zezwól na obniżenie wersji";
+/* No comment provided by engineer. */
+"Allow files and media only if your contact allows them." = "Zezwalaj na pliki i media tylko wtedy, gdy Twój kontakt na to pozwala.";
+
/* No comment provided by engineer. */
"Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Zezwalaj na nieodwracalne usuwanie wiadomości tylko wtedy, gdy Twój kontakt Ci na to pozwoli. (24 godziny)";
@@ -578,6 +599,9 @@ swipe action */
/* No comment provided by engineer. */
"Allow your contacts to send disappearing messages." = "Zezwól swoim kontaktom na wysyłanie znikających wiadomości.";
+/* No comment provided by engineer. */
+"Allow your contacts to send files and media." = "Pozwól kontaktom wysyłać pliki i media.";
+
/* No comment provided by engineer. */
"Allow your contacts to send voice messages." = "Zezwól swoim kontaktom na wysyłanie wiadomości głosowych.";
@@ -755,6 +779,9 @@ swipe action */
/* No comment provided by engineer. */
"Better groups" = "Lepsze grupy";
+/* No comment provided by engineer. */
+"Better groups performance" = "Lepsze działanie grup";
+
/* No comment provided by engineer. */
"Better message dates." = "Lepsze daty wiadomości.";
@@ -767,6 +794,9 @@ swipe action */
/* No comment provided by engineer. */
"Better notifications" = "Lepsze powiadomienia";
+/* No comment provided by engineer. */
+"Better privacy and security" = "Lepsza prywatność i bezpieczeństwo";
+
/* No comment provided by engineer. */
"Better security ✅" = "Lepsze zabezpieczenia ✅";
@@ -816,6 +846,9 @@ marked deleted chat item preview text */
/* No comment provided by engineer. */
"bold" = "pogrubiona";
+/* No comment provided by engineer. */
+"Bot" = "Bot";
+
/* No comment provided by engineer. */
"Both you and your contact can add message reactions." = "Zarówno Ty, jak i Twój kontakt możecie dodawać reakcje wiadomości.";
@@ -828,6 +861,9 @@ marked deleted chat item preview text */
/* No comment provided by engineer. */
"Both you and your contact can send disappearing messages." = "Zarówno Ty, jak i Twój kontakt możecie wysyłać znikające wiadomości.";
+/* No comment provided by engineer. */
+"Both you and your contact can send files and media." = "Zarówno Ty, jak i Twój kontakt możecie wysyłać pliki i media.";
+
/* No comment provided by engineer. */
"Both you and your contact can send voice messages." = "Zarówno Ty, jak i Twój kontakt możecie wysyłać wiadomości głosowe.";
@@ -1559,7 +1595,8 @@ swipe action */
/* No comment provided by engineer. */
"Delete message?" = "Usunąć wiadomość?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "Usuń wiadomości";
/* No comment provided by engineer. */
@@ -2876,7 +2913,7 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "Rola członka zostanie zmieniona na \"%@\". Członek otrzyma nowe zaproszenie.";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "Członek zostanie usunięty z grupy - nie można tego cofnąć!";
/* No comment provided by engineer. */
@@ -3711,7 +3748,7 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "Serwer przekaźnikowy chroni Twój adres IP, ale może obserwować czas trwania połączenia.";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "Usuń";
/* No comment provided by engineer. */
@@ -3723,7 +3760,7 @@ swipe action */
/* No comment provided by engineer. */
"Remove member" = "Usuń członka";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "Usunąć członka?";
/* No comment provided by engineer. */
diff --git a/apps/ios/product/README.md b/apps/ios/product/README.md
new file mode 100644
index 0000000000..107c0e6569
--- /dev/null
+++ b/apps/ios/product/README.md
@@ -0,0 +1,258 @@
+# SimpleX Chat iOS -- Product Overview
+
+> SimpleX Chat iOS product specification. Bidirectional code links: product docs reference source files, source files reference product docs.
+>
+> **Related spec:** [spec/README.md](../spec/README.md) | [spec/architecture.md](../spec/architecture.md)
+
+## Table of Contents
+
+1. [Vision](#vision)
+2. [Target Users](#target-users)
+3. [Capability Map](#capability-map)
+4. [Navigation Map](#navigation-map)
+5. [Related Specifications](#related-specifications)
+
+## Executive Summary
+
+SimpleX Chat is the first messaging platform with no user identifiers of any kind -- not even random numbers. It provides end-to-end encrypted messaging (with optional post-quantum cryptography), audio/video calls, file sharing, and group communication through a fully decentralized architecture where users control their own SMP relay servers. The iOS app is a native SwiftUI application backed by a Haskell core library.
+
+---
+
+## Vision
+
+SimpleX Chat is the first messaging platform that has no user identifiers -- not even random numbers. It uses double-ratchet end-to-end encryption with optional post-quantum cryptography. The system is fully decentralized with user-controlled SMP relay servers.
+
+The protocol design ensures that no server or network observer can determine who communicates with whom. Each conversation uses separate unidirectional messaging queues on potentially different servers, and there is no shared identifier between the sender and receiver queues.
+
+---
+
+## Target Users
+
+- **Privacy-conscious individuals** wanting secure messaging without phone-number or email-based identity
+- **Groups and communities** needing encrypted group communication with role-based access control
+- **Users avoiding identity linkage** who want to communicate without any persistent user identifier
+- **Organizations** needing self-hosted messaging infrastructure with full control over relay servers
+
+---
+
+## Capability Map
+
+### 1. Messaging
+
+Core message composition, delivery, and interaction features.
+
+| Feature | Description | Key Source (Swift) |
+|---------|-------------|--------------------|
+| Text with markdown | Rich text formatting with SimpleX markdown syntax | `Shared/Views/Chat/ComposeMessage/ComposeView.swift` |
+| Images | Compressed inline images (up to 255KB) | `Shared/Views/Chat/ChatItem/CIImageView.swift` |
+| Video | Video message recording and playback | `Shared/Views/Chat/ChatItem/CIVideoView.swift` |
+| Voice messages | Audio recording and playback (5min / 510KB limit) | `Shared/Views/Chat/ChatItem/CIVoiceView.swift` |
+| File sharing | Files up to 1GB via XFTP protocol | `Shared/Views/Chat/ChatItem/CIFileView.swift` |
+| Link previews | OpenGraph metadata extraction and display | `Shared/Views/Chat/ChatItem/CILinkView.swift` |
+| Message reactions | Emoji reactions on sent/received messages | `Shared/Views/Chat/ChatItem/EmojiItemView.swift` |
+| Message editing | Edit previously sent messages | `Shared/Views/Chat/ComposeMessage/ComposeView.swift` |
+| Message deletion | Broadcast delete (for recipient) or internal-only delete | `Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift` |
+| Timed messages | Self-destructing messages with configurable TTL | `Shared/Views/Chat/ChatItem/CIChatFeatureView.swift` |
+| Quoted replies | Reply to specific messages with quote context | `Shared/Views/Chat/ComposeMessage/ContextItemView.swift` |
+| Forwarding | Forward messages between chats | `Shared/Views/Chat/ChatItemForwardingView.swift` |
+| Search | Full-text search within conversations | `Shared/Views/Chat/ChatView.swift` |
+| Message reports | Report messages to group moderators | `Shared/Views/Chat/ChatView.swift` |
+
+### 2. Contacts
+
+Establishing, managing, and verifying contacts.
+
+| Feature | Description | Key Source (Swift) |
+|---------|-------------|--------------------|
+| Add via SimpleX address | Connect using a SimpleX contact address | `Shared/Views/NewChat/NewChatView.swift` |
+| Add via QR code | Scan QR code to establish connection | `Shared/Views/Chat/ScanCodeView.swift` |
+| Contact requests | Accept or reject incoming contact requests | `Shared/Views/ChatList/ContactRequestView.swift` |
+| Local aliases | Set private display names for contacts | `Shared/Views/Chat/ChatInfoView.swift` |
+| Contact verification | Compare security codes out-of-band | `Shared/Views/Chat/VerifyCodeView.swift` |
+| Blocking | Block contacts from sending messages | `Shared/Views/Chat/ChatInfoView.swift` |
+| Incognito mode | Per-contact random profile generation | `Shared/Views/UserSettings/IncognitoHelp.swift` |
+| Bot detection | Identify automated/bot contacts | `SimpleXChat/ChatTypes.swift` |
+
+### 3. Groups
+
+Multi-party encrypted conversations with role-based management.
+
+| Feature | Description | Key Source (Swift) |
+|---------|-------------|--------------------|
+| Create groups | Create new group with initial members | `Shared/Views/NewChat/AddGroupView.swift` |
+| Invite members | Invite by individual contact or link | `Shared/Views/Chat/Group/AddGroupMembersView.swift` |
+| Member roles | Owner, admin, moderator, member, observer | `SimpleXChat/ChatTypes.swift` |
+| Member admission | Queue-based admission with review workflow | `Shared/Views/Chat/Group/MemberAdmissionView.swift` |
+| Group links | Shareable invite links for groups | `Shared/Views/Chat/Group/GroupLinkView.swift` |
+| Business chat mode | Structured business communication groups | `Shared/Views/Chat/Group/GroupChatInfoView.swift` |
+| Content moderation | Member reports and moderator actions | `Shared/Views/Chat/Group/MemberSupportView.swift` |
+| Group preferences | Configure group-level feature settings | `Shared/Views/Chat/Group/GroupPreferencesView.swift` |
+| Member direct contacts | Establish direct chats from group membership | `Shared/Views/Chat/Group/GroupMemberInfoView.swift` |
+
+### 4. Calling
+
+End-to-end encrypted audio and video communication.
+
+| Feature | Description | Key Source (Swift) |
+|---------|-------------|--------------------|
+| E2E encrypted calls | Audio/video calls via WebRTC with E2E encryption | `Shared/Views/Call/WebRTCClient.swift` |
+| CallKit integration | Native iOS system call UI (ring, answer, decline) | `Shared/Views/Call/CallController.swift` |
+| Audio device switching | Switch between speaker, earpiece, Bluetooth | `Shared/Views/Call/CallAudioDeviceManager.swift` |
+| Call history | Call events displayed as chat items | `Shared/Views/Chat/ChatItem/CICallItemView.swift` |
+| Incoming call view | Dedicated UI for incoming call notifications | `Shared/Views/Call/IncomingCallView.swift` |
+
+### 5. Privacy & Security
+
+Encryption, authentication, and privacy controls.
+
+| Feature | Description | Key Source (Swift) |
+|---------|-------------|--------------------|
+| E2E encryption | Double-ratchet encryption for all messages | `SimpleXChat/API.swift` |
+| Post-quantum encryption | Optional PQ key exchange for direct chats | `SimpleXChat/ChatTypes.swift` |
+| Local authentication | Face ID, Touch ID, or app passcode lock | `Shared/Views/LocalAuth/LocalAuthView.swift` |
+| Hidden profiles | Password-protected profiles invisible in UI | `Shared/Views/UserSettings/HiddenProfileView.swift` |
+| Database encryption | AES encryption of local SQLite database | `Shared/Views/Database/DatabaseEncryptionView.swift` |
+| Screen privacy | Blur app content when in app switcher | `Shared/Views/UserSettings/PrivacySettings.swift` |
+| Encrypted file storage | Local files encrypted at rest | `SimpleXChat/CryptoFile.swift` |
+| Delivery receipts control | Toggle delivery/read receipts per contact/group | `Shared/Views/UserSettings/SetDeliveryReceiptsView.swift` |
+
+### 6. User Management
+
+Multiple profiles and identity management.
+
+| Feature | Description | Key Source (Swift) |
+|---------|-------------|--------------------|
+| Multiple profiles | Multiple user profiles within one app | `Shared/Views/UserSettings/UserProfilesView.swift` |
+| Active user switching | Switch between profiles via user picker | `Shared/Views/ChatList/UserPicker.swift` |
+| Incognito contacts | Per-contact random identities | `Shared/Views/UserSettings/IncognitoHelp.swift` |
+| Profile sharing | Share profile via contact address link | `Shared/Views/UserSettings/UserAddressView.swift` |
+| User muting | Mute notifications for specific profiles | `Shared/Views/ChatList/UserPicker.swift` |
+
+### 7. Network
+
+Server configuration, proxy support, and connectivity.
+
+| Feature | Description | Key Source (Swift) |
+|---------|-------------|--------------------|
+| Custom SMP servers | Configure personal SMP relay servers | `Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift` |
+| Custom XFTP servers | Configure personal XFTP file servers | `Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift` |
+| Tor/onion support | Route traffic through Tor .onion addresses | `Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift` |
+| SOCKS5 proxy | Route connections through SOCKS5 proxy | `Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift` |
+| Custom ICE servers | Configure WebRTC ICE/TURN servers | `Shared/Views/UserSettings/RTCServers.swift` |
+| Network timeouts | Configure connection timeout parameters | `Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift` |
+
+### 8. Customization
+
+Visual appearance and UI preferences.
+
+| Feature | Description | Key Source (Swift) |
+|---------|-------------|--------------------|
+| Themes | Light, dark, SimpleX, black, and custom themes | `Shared/Theme/ThemeManager.swift` |
+| Wallpapers | Preset and custom chat wallpapers | `Shared/Views/Helpers/ChatWallpaper.swift` |
+| Chat bubble styling | Customize message bubble appearance | `SimpleXChat/Theme/ThemeTypes.swift` |
+| One-handed UI mode | Compact layout for single-hand use | `Shared/Views/ChatList/OneHandUICard.swift` |
+| Language selection | In-app language override | `Shared/Views/UserSettings/AppearanceSettings.swift` |
+
+### 9. Data Management
+
+Import, export, encryption, and storage management.
+
+| Feature | Description | Key Source (Swift) |
+|---------|-------------|--------------------|
+| Export/import profiles | Full database export and import | `Shared/Views/Database/DatabaseView.swift` |
+| Database encryption | Encrypt/decrypt local database with passphrase | `Shared/Views/Database/DatabaseEncryptionView.swift` |
+| Local file encryption | Encrypt stored media and attachments | `SimpleXChat/CryptoFile.swift` |
+| Storage breakdown | View storage usage by category | `Shared/Views/UserSettings/StorageView.swift` |
+| Device-to-device migration | Migrate full profile between iOS devices | `Shared/Views/Migration/MigrateFromDevice.swift` |
+
+### 10. Desktop Integration
+
+Remote control of the mobile app from a desktop client.
+
+| Feature | Description | Key Source (Swift) |
+|---------|-------------|--------------------|
+| Remote control pairing | Pair with desktop app via QR code | `Shared/Views/RemoteAccess/ConnectDesktopView.swift` |
+| Session management | Manage active desktop control sessions | `Shared/Views/RemoteAccess/ConnectDesktopView.swift` |
+
+---
+
+## Navigation Map
+
+```
+Onboarding
+ OnboardingView.swift
+ -> SimpleXInfo -> CreateProfile -> ChooseServerOperators -> SetNotificationsMode -> CreateSimpleXAddress
+ -> ChatListView (home)
+
+ChatListView (home)
+ Shared/Views/ChatList/ChatListView.swift
+ -> ChatView .................. (tap conversation row)
+ -> NewChatMenuButton ......... (+ button)
+ -> SettingsView .............. (gear icon)
+ -> UserPicker ................ (avatar tap)
+ -> TagListView ............... (tag filter bar)
+ -> ServersSummaryView ........ (server status)
+
+ChatView
+ Shared/Views/Chat/ChatView.swift
+ -> ChatInfoView .............. (contact name tap, direct chat)
+ -> GroupChatInfoView ......... (group name tap, group chat)
+ -> ActiveCallView ............ (call button)
+ -> ComposeView ............... (message input area)
+ -> ChatItemInfoView .......... (long press -> info)
+ -> ChatItemForwardingView .... (long press -> forward)
+ -> SecondaryChatView ......... (member support thread)
+
+ChatInfoView
+ Shared/Views/Chat/ChatInfoView.swift
+ -> ContactPreferencesView .... (preferences)
+ -> VerifyCodeView ............ (verify security code)
+
+GroupChatInfoView
+ Shared/Views/Chat/Group/GroupChatInfoView.swift
+ -> GroupProfileView .......... (edit profile)
+ -> AddGroupMembersView ....... (invite members)
+ -> GroupLinkView ............. (manage group link)
+ -> MemberAdmissionView ....... (admission settings)
+ -> GroupPreferencesView ...... (group feature settings)
+ -> GroupMemberInfoView ....... (tap member)
+ -> GroupWelcomeView .......... (welcome message)
+
+NewChatMenuButton
+ Shared/Views/NewChat/NewChatMenuButton.swift
+ -> NewChatView ............... (QR scanner / paste link)
+ -> AddGroupView .............. (create group)
+ -> UserAddressView ........... (create SimpleX address)
+
+SettingsView
+ Shared/Views/UserSettings/SettingsView.swift
+ -> AppearanceSettings ........ (themes, wallpapers, UI)
+ -> NetworkAndServers ......... (SMP/XFTP/proxy config)
+ -> PrivacySettings ........... (privacy toggles)
+ -> NotificationsView ......... (push notification mode)
+ -> DatabaseView .............. (export/import/encrypt)
+ -> CallSettings .............. (call preferences)
+ -> StorageView ............... (storage usage)
+ -> VersionView ............... (about/version)
+ -> DeveloperView ............. (developer options)
+
+UserPicker
+ Shared/Views/ChatList/UserPicker.swift
+ -> UserProfilesView .......... (manage all profiles)
+ -> UserAddressView ........... (SimpleX address)
+ -> PreferencesView ........... (user preferences)
+ -> SettingsView .............. (app settings)
+ -> ConnectDesktopView ........ (pair with desktop)
+```
+
+---
+
+## Related Specifications
+
+- [concepts.md](concepts.md) -- Feature concept index with bidirectional code links
+- [glossary.md](glossary.md) -- Domain term glossary
+- [spec/README.md](../spec/README.md) -- Technical specification overview
+- [spec/architecture.md](../spec/architecture.md) -- Architecture specification
+- Haskell core: `../../src/Simplex/Chat/Controller.hs`, `../../src/Simplex/Chat/Types.hs`
+- Swift model: `Shared/Model/ChatModel.swift`, `SimpleXChat/ChatTypes.swift`
+- Swift API bridge: `SimpleXChat/API.swift`, `Shared/Model/SimpleXAPI.swift`
diff --git a/apps/ios/product/concepts.md b/apps/ios/product/concepts.md
new file mode 100644
index 0000000000..a60fe98cbb
--- /dev/null
+++ b/apps/ios/product/concepts.md
@@ -0,0 +1,83 @@
+# SimpleX Chat iOS -- Concept Index
+
+> SimpleX Chat iOS concept index. Maps every product concept to its documentation and source code with bidirectional links.
+>
+> **Related spec:** [spec/api.md](../spec/api.md) | [spec/state.md](../spec/state.md) | [spec/architecture.md](../spec/architecture.md)
+
+## Table of Contents
+
+1. [Feature Concepts](#section-1-feature-concepts)
+2. [Entity Index](#section-2-entity-index)
+
+## Executive Summary
+
+This document provides a structured mapping between product-level concepts, their documentation, and their implementation in both the Swift iOS layer and the Haskell core library. All source paths are relative to `apps/ios/` for Swift and use `../../src/` prefix for Haskell files (relative to `apps/ios/`).
+
+---
+
+## Section 1: Feature Concepts
+
+| # | Concept | Product Docs | Spec Docs | Source Files (Swift) | Source Files (Haskell) |
+|---|---------|-------------|-----------|---------------------|----------------------|
+| 1 | Chat List | [views/chat-list.md](views/chat-list.md), [views/onboarding.md](views/onboarding.md) | [spec/client/chat-list.md](../spec/client/chat-list.md) | `Shared/Views/ChatList/ChatListView.swift` | `Controller.hs` (`APIGetChats`) |
+| 2 | Direct Chat | [views/chat.md](views/chat.md), [flows/messaging.md](flows/messaging.md) | [spec/client/chat-view.md](../spec/client/chat-view.md) | `Shared/Views/Chat/ChatView.swift`, `ChatInfoView.swift` | `Types.hs` (`Contact`), `Messages.hs` |
+| 3 | Group Chat | [views/chat.md](views/chat.md), [views/group-info.md](views/group-info.md) | [spec/client/chat-view.md](../spec/client/chat-view.md) | `Shared/Views/Chat/ChatView.swift`, `Group/GroupChatInfoView.swift` | `Types.hs` (`GroupInfo`, `GroupMember`) |
+| 4 | Message Composition | [views/chat.md](views/chat.md) | [spec/client/compose.md](../spec/client/compose.md) | `ComposeMessage/ComposeView.swift`, `SendMessageView.swift` | `Controller.hs` (`APISendMessages`) |
+| 5 | Message Reactions | [views/chat.md](views/chat.md) | [spec/api.md](../spec/api.md) | `ChatItem/EmojiItemView.swift` | `Controller.hs` (`APIChatItemReaction`) |
+| 6 | Message Editing | [views/chat.md](views/chat.md) | [spec/client/compose.md](../spec/client/compose.md) | `ComposeMessage/ComposeView.swift`, `ChatItemInfoView.swift` | `Controller.hs` (`APIUpdateChatItem`) |
+| 7 | Message Deletion | [views/chat.md](views/chat.md) | [spec/api.md](../spec/api.md) | `ChatItem/MarkedDeletedItemView.swift`, `DeletedItemView.swift` | `Controller.hs` (`APIDeleteChatItem`) |
+| 8 | Timed Messages | [views/chat.md](views/chat.md) | [spec/api.md](../spec/api.md) | `ChatItem/CIChatFeatureView.swift` | `Types/Preferences.hs` (`TimedMessagesPreference`) |
+| 9 | Voice Messages | [views/chat.md](views/chat.md) | [spec/client/compose.md](../spec/client/compose.md) | `ChatItem/CIVoiceView.swift`, `ComposeVoiceView.swift` | `Protocol.hs` (`MCVoice`) |
+| 10 | File Transfer | [flows/file-transfer.md](flows/file-transfer.md) | [spec/services/files.md](../spec/services/files.md) | `ChatItem/CIFileView.swift`, `SimpleXChat/FileUtils.swift` | `Files.hs`, `Store/Files.hs` |
+| 11 | Link Previews | [views/chat.md](views/chat.md) | [spec/client/chat-view.md](../spec/client/chat-view.md) | `ChatItem/CILinkView.swift`, `ComposeLinkView.swift` | `Protocol.hs` (`MCLink`) |
+| 12 | Contact Connection | [flows/connection.md](flows/connection.md), [views/new-chat.md](views/new-chat.md) | [spec/api.md](../spec/api.md) | `NewChat/NewChatView.swift`, `QRCode.swift` | `Controller.hs` (`APIConnect`, `APIAddContact`) |
+| 13 | Contact Verification | [views/contact-info.md](views/contact-info.md) | [spec/api.md](../spec/api.md) | `Shared/Views/Chat/VerifyCodeView.swift` | `Controller.hs` (`APIVerifyContact`) |
+| 14 | Group Management | [flows/group-lifecycle.md](flows/group-lifecycle.md) | [spec/api.md](../spec/api.md), [spec/database.md](../spec/database.md) | `NewChat/AddGroupView.swift`, `Group/GroupChatInfoView.swift` | `Controller.hs` (`APINewGroup`), `Store/Groups.hs` |
+| 15 | Group Links | [views/group-info.md](views/group-info.md) | [spec/api.md](../spec/api.md) | `Group/GroupLinkView.swift` | `Controller.hs` (`APICreateGroupLink`) |
+| 16 | Member Roles | [views/group-info.md](views/group-info.md) | [spec/api.md](../spec/api.md) | `SimpleXChat/ChatTypes.swift`, `Group/GroupMemberInfoView.swift` | `Types/Shared.hs` (`GroupMemberRole`) |
+| 17 | Audio/Video Calls | [views/call.md](views/call.md), [flows/calling.md](flows/calling.md) | [spec/services/calls.md](../spec/services/calls.md) | `Call/ActiveCallView.swift`, `CallController.swift`, `WebRTCClient.swift` | `Call.hs` (`RcvCallInvitation`, `CallType`) |
+| 18 | Push Notifications | [views/settings.md](views/settings.md) | [spec/services/notifications.md](../spec/services/notifications.md) | `Model/NtfManager.swift`, `SimpleX NSE/NotificationService.swift` | `Controller.hs` |
+| 19 | User Profiles | [views/user-profiles.md](views/user-profiles.md) | [spec/state.md](../spec/state.md), [spec/client/navigation.md](../spec/client/navigation.md) | `UserSettings/UserProfilesView.swift`, `ChatList/UserPicker.swift` | `Types.hs` (`User`), `Store/Profiles.hs` |
+| 20 | Incognito Mode | [views/contact-info.md](views/contact-info.md) | [spec/api.md](../spec/api.md) | `UserSettings/IncognitoHelp.swift` | `ProfileGenerator.hs`, `Types.hs` |
+| 21 | Hidden Profiles | [views/user-profiles.md](views/user-profiles.md) | [spec/api.md](../spec/api.md) | `UserSettings/HiddenProfileView.swift` | `Controller.hs` (`APIHideUser`, `APIUnhideUser`) |
+| 22 | Local Authentication | [views/settings.md](views/settings.md) | [spec/architecture.md](../spec/architecture.md) | `LocalAuth/LocalAuthView.swift`, `PasscodeView.swift` | N/A (iOS-only) |
+| 23 | Database Encryption | [views/settings.md](views/settings.md) | [spec/database.md](../spec/database.md) | `Database/DatabaseEncryptionView.swift`, `DatabaseView.swift` | `Controller.hs` (`APIExportArchive`) |
+| 24 | Theme System | [views/settings.md](views/settings.md) | [spec/services/theme.md](../spec/services/theme.md) | `Theme/ThemeManager.swift`, `SimpleXChat/Theme/ThemeTypes.swift` | `Types/UITheme.hs` |
+| 25 | Network Configuration | [views/settings.md](views/settings.md) | [spec/architecture.md](../spec/architecture.md) | `NetworkAndServers/NetworkAndServers.swift`, `ProtocolServersView.swift` | `Controller.hs` (`APISetNetworkConfig`) |
+| 26 | Device Migration | [flows/onboarding.md](flows/onboarding.md) | [spec/database.md](../spec/database.md) | `Migration/MigrateFromDevice.swift`, `MigrateToDevice.swift` | `Archive.hs` |
+| 27 | Remote Desktop | [views/settings.md](views/settings.md) | [spec/architecture.md](../spec/architecture.md) | `RemoteAccess/ConnectDesktopView.swift` | `Remote.hs`, `Remote/Types.hs` |
+| 28 | Chat Tags | [views/chat-list.md](views/chat-list.md) | [spec/state.md](../spec/state.md) | `ChatList/TagListView.swift`, `ChatListView.swift` | `Types.hs` (`ChatTag`), `Controller.hs` |
+| 29 | User Address | [views/settings.md](views/settings.md) | [spec/api.md](../spec/api.md) | `UserSettings/UserAddressView.swift`, `Onboarding/AddressCreationCard.swift` | `Controller.hs` (`APICreateMyAddress`) |
+| 30 | Member Support Chat | [views/group-info.md](views/group-info.md) | [spec/api.md](../spec/api.md) | `Group/MemberSupportView.swift`, `MemberAdmissionView.swift` | `Messages.hs` (`GroupChatScope`), `Controller.hs` |
+
+---
+
+## Section 2: Entity Index
+
+Core data entities, their storage, and the operations that manage their lifecycle.
+
+| Entity | DB Table (Haskell) | Created By | Read By | Mutated By | Deleted By |
+|--------|-------------------|------------|---------|------------|------------|
+| **User** | `users` | `CreateActiveUser` in `Controller.hs` | `ListUsers`, `APISetActiveUser` in `Controller.hs` | `APISetActiveUser`, `APIHideUser`, `APIUnhideUser`, `APIMuteUser`, `APIUpdateProfile` in `Controller.hs` | `APIDeleteUser` in `Controller.hs`; `Store/Profiles.hs` |
+| **Contact** | `contacts`, `contact_profiles` | `APIAddContact`, `APIConnect` in `Controller.hs` | `APIGetChat` in `Controller.hs`; `Store/Direct.hs` (getContact) | `APISetContactAlias`, `APISetConnectionAlias` in `Controller.hs`; `Store/Direct.hs` | `APIDeleteChat` in `Controller.hs`; `Store/Direct.hs` (deleteContact) |
+| **GroupInfo** | `groups`, `group_profiles` | `APINewGroup` in `Controller.hs`; `Store/Groups.hs` (createNewGroup) | `APIGetChat`, `APIGroupInfo` in `Controller.hs`; `Store/Groups.hs` | `APIUpdateGroupProfile` in `Controller.hs`; `Store/Groups.hs` (updateGroupProfile) | `APIDeleteChat` in `Controller.hs`; `Store/Groups.hs` (deleteGroup) |
+| **GroupMember** | `group_members`, `contact_profiles` | `APIAddMember`, `APIJoinGroup` in `Controller.hs`; `Store/Groups.hs` (createNewGroupMember) | `APIListMembers` in `Controller.hs`; `Store/Groups.hs` (getGroupMembers) | `APIMembersRole` in `Controller.hs`; `Store/Groups.hs` (updateGroupMemberRole) | `APIRemoveMembers` in `Controller.hs`; `Store/Groups.hs` (deleteGroupMember) |
+| **ChatItem** | `chat_items`, `chat_item_versions` | `APISendMessages` in `Controller.hs`; `Store/Messages.hs` (createNewChatItem) | `APIGetChat`, `APIGetChatItems` in `Controller.hs`; `Store/Messages.hs` (getChatItems) | `APIUpdateChatItem`, `APIChatItemReaction` in `Controller.hs`; `Store/Messages.hs` (updateChatItem) | `APIDeleteChatItem` in `Controller.hs`; `Store/Messages.hs` (deleteChatItem) |
+| **Connection** | `connections` | `createConnection` via SMP agent; `Store/Connections.hs` | `Store/Connections.hs` (getConnectionEntity) | `Store/Connections.hs` (updateConnectionStatus) | `Store/Connections.hs` (deleteConnection) |
+| **FileTransfer** | `files`, `snd_files`, `rcv_files`, `xftp_file_descriptions` | `APISendMessages` (with file), `ReceiveFile` in `Controller.hs`; `Store/Files.hs` | `Store/Files.hs` (getFileTransfer) | `Store/Files.hs` (updateFileStatus, updateFileProgress) | `Store/Files.hs` (deleteFileTransfer) |
+| **GroupLink** | `user_contact_links` | `APICreateGroupLink` in `Controller.hs`; `Store/Groups.hs` | `APIGetGroupLink` in `Controller.hs`; `Store/Groups.hs` | N/A (recreated on change) | `APIDeleteGroupLink` in `Controller.hs`; `Store/Groups.hs` |
+| **ChatTag** | `chat_tags`, `chat_tags_chats` | `APICreateChatTag` in `Controller.hs` | `APIGetChats` in `Controller.hs` | `APIUpdateChatTag`, `APISetChatTags` in `Controller.hs` | `APIDeleteChatTag` in `Controller.hs` |
+| **RcvCallInvitation** | In-memory (not persisted) | Received via `XCallInv` message in `Library/Subscriber.hs`; stored in `ChatModel.callInvitations` | `CallController.swift`, `IncomingCallView.swift` | Updated on call accept/reject in `CallManager.swift` | Removed on call end/reject; `Controller.hs` |
+
+---
+
+## Cross-References
+
+- Product overview: [README.md](README.md)
+- Glossary: [glossary.md](glossary.md)
+- Haskell core controller: `../../src/Simplex/Chat/Controller.hs`
+- Haskell core types: `../../src/Simplex/Chat/Types.hs`
+- Haskell store layer: `../../src/Simplex/Chat/Store/` (Direct.hs, Groups.hs, Messages.hs, Files.hs, Profiles.hs, Connections.hs)
+- Swift model: `Shared/Model/ChatModel.swift`
+- Swift API types: `SimpleXChat/APITypes.swift`, `SimpleXChat/ChatTypes.swift`
+- Swift API bridge: `SimpleXChat/API.swift`, `Shared/Model/SimpleXAPI.swift`
diff --git a/apps/ios/product/flows/calling.md b/apps/ios/product/flows/calling.md
new file mode 100644
index 0000000000..86cb026625
--- /dev/null
+++ b/apps/ios/product/flows/calling.md
@@ -0,0 +1,179 @@
+# Audio/Video Call Flow
+
+> **Related spec:** [spec/services/calls.md](../../spec/services/calls.md)
+
+## Overview
+
+WebRTC-based audio and video calling in SimpleX Chat iOS. Calls are end-to-end encrypted with an additional shared key negotiated over the E2E encrypted SMP channel. The iOS app integrates with CallKit for native call UI (incoming call screen, lock screen integration) with a fallback mode for regions where CallKit is restricted (China). Call signaling (offer/answer/ICE candidates) is exchanged via SMP messages, not through a central signaling server.
+
+## Prerequisites
+
+- Established direct contact connection (calls are 1:1 only, not available in groups)
+- Microphone permission granted (audio calls)
+- Camera permission granted (video calls)
+- Network connectivity for WebRTC peer-to-peer or relay
+
+## Step-by-Step Processes
+
+### 1. Initiate Call
+
+1. User opens a direct chat in `ChatView`.
+2. Taps the audio or video call button in the navigation bar.
+3. `CallController` determines call type: `CallType(media: .audio/.video, capabilities: CallCapabilities(encryption: true))`.
+4. If CallKit is enabled (`CallController.useCallKit()`):
+ - `CXStartCallAction` is requested via `CXCallController`.
+ - CallKit reports the outgoing call.
+ - `provider(perform: CXStartCallAction)` fulfills and reports `reportOutgoingCall(startedConnectingAt:)`.
+5. Calls `apiSendCallInvitation(contact:callType:)`:
+ ```swift
+ func apiSendCallInvitation(_ contact: Contact, _ callType: CallType) async throws
+ ```
+6. Sends `ChatCommand.apiSendCallInvitation(contact:callType:)`.
+7. Core sends the call invitation to the contact via SMP.
+8. `ChatModel.shared.activeCall` is set with the call state.
+
+### 2. Receive Call
+
+1. `ChatReceiver` receives `ChatEvent.callInvitation(callInvitation: RcvCallInvitation)`.
+2. `RcvCallInvitation` contains: `user`, `contact`, `callType`, `sharedKey`, `callUUID`, `callTs`.
+3. Processing in `processReceivedMsg`:
+ - Call invitation is stored in `chatModel.callInvitations`.
+4. If CallKit is enabled:
+ - `CXProvider.reportNewIncomingCall` presents the native iOS incoming call UI.
+ - Works even on lock screen and in background.
+5. If CallKit is disabled (China / user preference):
+ - `IncomingCallView` is shown as an in-app overlay.
+ - `SoundPlayer` plays the ringtone.
+6. User chooses to accept or reject.
+
+### 3. Accept Call
+
+1. **Via CallKit**: User swipes to accept on the native incoming call screen.
+ - `provider(perform: CXAnswerCallAction)` is triggered.
+ - Waits for chat to be started if needed (`waitUntilChatStarted(timeoutMs: 30_000)`).
+ - `callManager.answerIncomingCall(callUUID:)` begins WebRTC setup.
+ - `fulfillOnConnect` is set -- the action is fulfilled only when WebRTC reaches connected state (required for audio/mic to work on lock screen).
+2. **Via in-app UI**: User taps "Accept" in `IncomingCallView`.
+ - Directly starts WebRTC setup.
+
+### 4. Reject Call
+
+1. **Via CallKit**: User taps "Decline" on native UI.
+ - `provider(perform: CXEndCallAction)` is triggered.
+ - `callManager.endCall(callUUID:)` cleans up.
+2. **Via API**: `apiRejectCall(contact:)` sends rejection to peer.
+3. Call invitation is removed from `chatModel.callInvitations`.
+
+### 5. WebRTC Setup (Signaling)
+
+All signaling messages are exchanged via E2E encrypted SMP messages (no central signaling server).
+
+**Caller side:**
+1. `WebRTCClient` creates a `RTCPeerConnection`.
+2. Creates SDP offer.
+3. Calls `apiSendCallOffer(contact:rtcSession:rtcIceCandidates:media:capabilities:)`:
+ ```swift
+ func apiSendCallOffer(_ contact: Contact, _ rtcSession: String, _ rtcIceCandidates: String,
+ media: CallMediaType, capabilities: CallCapabilities) async throws
+ ```
+4. Constructs `WebRTCCallOffer(callType:rtcSession:)` and sends via `ChatCommand.apiSendCallOffer`.
+5. Gathers ICE candidates and sends via `apiSendCallExtraInfo(contact:rtcIceCandidates:)`.
+
+**Callee side:**
+1. Receives the offer via SMP.
+2. `WebRTCClient` sets remote description from the offer.
+3. Creates SDP answer.
+4. Calls `apiSendCallAnswer(contact:rtcSession:rtcIceCandidates:)`:
+ ```swift
+ func apiSendCallAnswer(_ contact: Contact, _ rtcSession: String, _ rtcIceCandidates: String) async throws
+ ```
+5. Constructs `WebRTCSession(rtcSession:rtcIceCandidates:)` and sends.
+6. Gathers and sends additional ICE candidates via `apiSendCallExtraInfo`.
+
+### 6. Media Streaming
+
+1. WebRTC peer connection transitions to connected state.
+2. If CallKit is used, `fulfillOnConnect` action is fulfilled (enables audio hardware).
+3. Audio/video streams are active.
+4. `ActiveCallView` displays:
+ - Remote video (full screen)
+ - Local video preview (picture-in-picture corner)
+ - Call controls: mute, speaker, camera toggle, end call
+ - Call duration timer
+5. `CallViewRenderers` manages WebRTC video rendering surfaces.
+6. Call status updates are sent via `apiCallStatus(contact:status:)`.
+
+### 7. Audio Routing
+
+1. `CallAudioDeviceManager` handles audio device selection.
+2. Options: earpiece (receiver), speaker, Bluetooth devices.
+3. `AudioDevicePicker` provides UI for device selection during call.
+4. Uses `AVAudioSession` for routing configuration.
+
+### 8. End Call
+
+1. Either party taps "End" button.
+2. Calls `apiEndCall(contact:)`:
+ ```swift
+ func apiEndCall(_ contact: Contact) async throws
+ ```
+3. Sends `ChatCommand.apiEndCall(contact:)` via SMP to notify peer.
+4. `WebRTCClient` closes peer connection, releases media resources.
+5. If CallKit: `CXEndCallAction` is requested, `provider(perform: CXEndCallAction)` fulfills.
+6. `ChatModel.shared.activeCall` is cleared.
+7. A `CICallItemView` event item is added to the chat history (call duration, type).
+
+### 9. CallKit-Free Mode
+
+1. `CallController.isInChina` checks `SKStorefront().countryCode == "CHN"`.
+2. If in China or user disabled CallKit (`callKitEnabledGroupDefault`): `useCallKit()` returns `false`.
+3. Incoming calls use `IncomingCallView` overlay instead of native CallKit UI.
+4. `SoundPlayer` handles ringtone playback.
+5. No lock-screen call answering; app must be in foreground or notified via push.
+
+## Data Structures
+
+| Type | Location | Description |
+|------|----------|-------------|
+| `CallType` | `SimpleXChat/CallTypes.swift` | `media: CallMediaType` (.audio/.video), `capabilities: CallCapabilities` |
+| `CallMediaType` | `SimpleXChat/CallTypes.swift` | `.audio` or `.video` |
+| `CallCapabilities` | `SimpleXChat/CallTypes.swift` | `encryption: Bool` for E2E call encryption support |
+| `RcvCallInvitation` | `SimpleXChat/CallTypes.swift` | Incoming call: user, contact, callType, sharedKey, callUUID, callTs |
+| `WebRTCCallOffer` | `SimpleXChat/CallTypes.swift` | SDP offer with call type and WebRTC session data |
+| `WebRTCSession` | `SimpleXChat/CallTypes.swift` | `rtcSession` (SDP) and `rtcIceCandidates` (serialized) |
+| `WebRTCExtraInfo` | `SimpleXChat/CallTypes.swift` | Additional ICE candidates sent after initial offer/answer |
+| `WebRTCCallStatus` | `SimpleXChat/CallTypes.swift` | Call lifecycle states for status reporting |
+| `CallMediaSource` | `SimpleXChat/CallTypes.swift` | `.mic`, `.camera`, `.screenAudio`, `.screenVideo`, `.unknown` |
+| `VideoCamera` | `SimpleXChat/CallTypes.swift` | `.user` (front) or `.environment` (rear) camera |
+
+## Error Cases
+
+| Error | Cause | Handling |
+|-------|-------|----------|
+| Chat not ready on CallKit answer | App suspended, slow startup | `waitUntilChatStarted` with 30s timeout; `action.fail()` on timeout |
+| Call invitation not found | Race condition between notification and event processing | `justRefreshCallInvitations()` retry |
+| WebRTC peer connection failure | NAT traversal, network issues | Call ends with error status |
+| CallKit action fail | Internal state mismatch | `action.fail()` called, call cleaned up |
+| No camera/mic permission | User denied permissions | Permission request dialog shown |
+
+## Key Files
+
+| File | Purpose |
+|------|---------|
+| `Shared/Views/Call/CallController.swift` | CallKit integration, CXProvider delegate, PKPushRegistry, call lifecycle management |
+| `Shared/Views/Call/CallManager.swift` | Call state management, starting/answering/ending calls |
+| `Shared/Views/Call/WebRTCClient.swift` | WebRTC peer connection, SDP offer/answer, ICE candidate handling |
+| `Shared/Views/Call/ActiveCallView.swift` | Active call UI: video renderers, controls, duration |
+| `Shared/Views/Call/CallViewRenderers.swift` | WebRTC video rendering surfaces |
+| `Shared/Views/Call/IncomingCallView.swift` | Non-CallKit incoming call overlay |
+| `Shared/Views/Call/CallAudioDeviceManager.swift` | Audio routing: speaker, earpiece, Bluetooth |
+| `Shared/Views/Call/AudioDevicePicker.swift` | Audio device selection UI |
+| `Shared/Views/Call/SoundPlayer.swift` | Ringtone and call sound playback |
+| `Shared/Views/Call/WebRTC.swift` | WebRTC configuration and utilities |
+| `SimpleXChat/CallTypes.swift` | All call-related type definitions |
+| `Shared/Model/SimpleXAPI.swift` | Call API functions: `apiSendCallInvitation`, `apiSendCallOffer`, `apiSendCallAnswer`, `apiSendCallExtraInfo`, `apiEndCall`, `apiRejectCall`, `apiCallStatus` |
+
+## Related Specifications
+
+- `apps/ios/product/README.md` -- Product overview: Calls capability
+- `apps/ios/product/flows/connection.md` -- Calls require an established direct connection
diff --git a/apps/ios/product/flows/connection.md b/apps/ios/product/flows/connection.md
new file mode 100644
index 0000000000..7b9c8ee304
--- /dev/null
+++ b/apps/ios/product/flows/connection.md
@@ -0,0 +1,159 @@
+# Connection Flow
+
+> **Related spec:** [spec/api.md](../../spec/api.md) | [spec/architecture.md](../../spec/architecture.md)
+
+## Overview
+
+Establishing contact between two SimpleX Chat users. SimpleX uses no user identifiers; connections are formed through one-time invitation links or permanent SimpleX addresses. Each connection creates unique unidirectional SMP queues, ensuring no server can correlate sender and receiver. Supports incognito mode for per-contact random profile generation.
+
+## Prerequisites
+
+- User profile created and chat engine running
+- Network connectivity to SMP relay servers
+- For QR code scanning: camera permission granted
+
+## Step-by-Step Processes
+
+### 1. Create Invitation Link
+
+1. User taps "+" button in `ChatListView` -> `NewChatMenuButton` -> "Add contact".
+2. `NewChatView` is presented.
+3. Calls `apiAddContact(incognito:)`:
+ ```swift
+ func apiAddContact(incognito: Bool) async
+ -> ((CreatedConnLink, PendingContactConnection)?, Alert?)
+ ```
+4. Internally sends `ChatCommand.apiAddContact(userId:incognito:)` to core.
+5. Core creates SMP queues and returns `ChatResponse1.invitation(user, connLinkInv, connection)`.
+6. Returns `(CreatedConnLink, PendingContactConnection)`.
+7. `CreatedConnLink` contains the invitation URI (both full and short link forms).
+8. UI displays:
+ - QR code rendered by `QRCode` view (scannable by peer)
+ - Share button to send link via system share sheet
+ - Copy button for clipboard
+9. A `PendingContactConnection` appears in the chat list while awaiting peer.
+
+### 2. Connect via Link
+
+1. User receives a SimpleX link (pasted, scanned, or opened via URL scheme).
+2. If opened via deep link: `SimpleXApp.onOpenURL` sets `chatModel.appOpenUrl`.
+3. For manual entry: User pastes link in `NewChatView`.
+4. First, `apiConnectPlan(connLink:inProgress:)` is called to validate:
+ ```swift
+ func apiConnectPlan(connLink: String, inProgress: BoxedValue) async
+ -> ((CreatedConnLink, ConnectionPlan)?, Alert?)
+ ```
+5. Returns `ConnectionPlan` indicating whether it is an invitation, contact address, or group link, and whether connection is already established.
+6. If valid, calls `apiConnect(incognito:connLink:)`:
+ ```swift
+ func apiConnect(incognito: Bool, connLink: CreatedConnLink) async
+ -> (ConnReqType, PendingContactConnection)?
+ ```
+7. Core creates the connection and returns one of:
+ - `ChatResponse1.sentConfirmation(user, connection)` -- for invitation links (type: `.invitation`)
+ - `ChatResponse1.sentInvitation(user, connection)` -- for contact address links (type: `.contact`)
+ - `ChatResponse1.contactAlreadyExists(user, contact)` -- duplicate
+8. `PendingContactConnection` appears in chat list while awaiting peer confirmation.
+
+### 3. Prepared Contact/Group Flow (Short Links)
+
+1. For short links with embedded profile data, the app uses a two-phase flow.
+2. `apiPrepareContact(connLink:contactShortLinkData:)` or `apiPrepareGroup(connLink:groupShortLinkData:)` creates a local prepared chat.
+3. Returns `ChatData` with the prepared contact/group shown in UI before connecting.
+4. User can switch profiles or set incognito before committing.
+5. `apiConnectPreparedContact(contactId:incognito:msg:)` finalizes the connection.
+6. Returns `ChatResponse1.startedConnectionToContact(user, contact)`.
+
+### 4. Accept Contact Request
+
+1. When a peer connects via the user's SimpleX address, core generates a `ChatEvent.receivedContactRequest`.
+2. `processReceivedMsg` handles the event, adding a `UserContactRequest` to `ChatModel`.
+3. Contact request appears in `ChatListView` as a special `ContactRequestView` row.
+4. User taps "Accept":
+ ```swift
+ func apiAcceptContactRequest(incognito: Bool, contactReqId: Int64) async -> Contact?
+ ```
+5. Sends `ChatCommand.apiAcceptContact(incognito:contactReqId:)`.
+6. Core returns `ChatResponse1.acceptingContactRequest(user, contact)`.
+7. Connection handshake proceeds asynchronously.
+8. User can also reject: `apiRejectContactRequest(contactReqId:)` -> `ChatResponse1.contactRequestRejected`.
+
+### 5. Connection Established
+
+1. Both sides complete the SMP handshake asynchronously.
+2. Core sends `ChatEvent.contactConnected(user, contact, userCustomProfile)`.
+3. `processReceivedMsg` updates `ChatModel`:
+ - Contact status transitions from pending to active.
+ - Chat becomes available for messaging.
+4. `NtfManager` may post a notification: "Contact connected".
+5. The `PendingContactConnection` in the chat list is replaced by the full contact chat.
+
+### 6. Create SimpleX Address
+
+1. User navigates to Settings or taps "Create SimpleX address" during onboarding.
+2. Calls `apiCreateUserAddress()`:
+ ```swift
+ func apiCreateUserAddress() async throws -> CreatedConnLink?
+ ```
+3. Core creates a permanent address (unlike one-time invitations).
+4. Address is stored in `ChatModel.shared.userAddress`.
+5. Can be shared publicly; multiple contacts can connect via the same address.
+6. User must accept each incoming contact request individually.
+7. To delete: `apiDeleteUserAddress()` removes the address and associated SMP queues.
+
+### 7. Incognito Connection
+
+1. Before connecting, user toggles "Incognito" in the connection UI.
+2. `incognito: true` is passed to `apiAddContact`, `apiConnect`, or `apiAcceptContactRequest`.
+3. Core generates a random display name for this connection only.
+4. The random profile is stored per-connection; the user's real profile is never shared.
+5. Incognito status is shown with a mask icon in the chat.
+6. Can also be toggled for pending connections via `apiSetConnectionIncognito(connId:incognito:)`.
+
+## Data Structures
+
+| Type | Location | Description |
+|------|----------|-------------|
+| `CreatedConnLink` | `SimpleXChat/APITypes.swift` | Contains `connFullLink` (URI) and optional `connShortLink` |
+| `PendingContactConnection` | `SimpleXChat/ChatTypes.swift` | Represents an in-progress connection before contact is established |
+| `ConnectionPlan` | `Shared/Model/AppAPITypes.swift` | Enum describing what a link will do: connect contact, join group, already connected, etc. |
+| `ConnReqType` | `Shared/Views/NewChat/NewChatView.swift` | `.invitation`, `.contact`, or `.groupLink` -- type of connection request |
+| `Contact` | `SimpleXChat/ChatTypes.swift` | Full contact model with profile, connection status, preferences |
+| `UserContactRequest` | `SimpleXChat/ChatTypes.swift` | Incoming contact request awaiting acceptance |
+| `ChatType` | `SimpleXChat/ChatTypes.swift` | `.direct`, `.group`, `.local`, `.contactRequest`, `.contactConnection` |
+
+## Error Cases
+
+| Error | Cause | Handling |
+|-------|-------|----------|
+| `ChatError.invalidConnReq` | Malformed or expired link | Alert: "Invalid connection link" |
+| `ChatError.unsupportedConnReq` | Link requires newer app version | Alert: "Unsupported connection link" |
+| `ChatError.errorAgent(.SMP(_, .AUTH))` | Link already used or deleted | Alert: "Connection error (AUTH)" |
+| `ChatError.errorAgent(.SMP(_, .BLOCKED(info)))` | Server operator blocked connection | Alert: "Connection blocked" with reason |
+| `ChatError.errorAgent(.SMP(_, .QUOTA))` | Too many undelivered messages | Alert: "Undelivered messages" |
+| `ChatError.errorAgent(.INTERNAL("SEUniqueID"))` | Duplicate connection attempt | Alert: "Already connected?" |
+| `ChatError.errorAgent(.BROKER(_, .TIMEOUT))` | Server timeout | Retryable via `chatApiSendCmdWithRetry` |
+| `ChatError.errorAgent(.BROKER(_, .NETWORK))` | Network failure | Retryable via `chatApiSendCmdWithRetry` |
+| `contactAlreadyExists` | Connecting to existing contact | Alert: "Contact already exists" with contact name |
+| `errorAgent(.SMP(_, .AUTH))` on accept | Sender deleted request | Alert: "Sender may have deleted the connection request" |
+
+## Key Files
+
+| File | Purpose |
+|------|---------|
+| `Shared/Views/NewChat/NewChatView.swift` | Main connection UI: create link, paste link, QR scan |
+| `Shared/Views/NewChat/NewChatMenuButton.swift` | "+" button menu in chat list |
+| `Shared/Views/NewChat/QRCode.swift` | QR code rendering for invitation links |
+| `Shared/Views/NewChat/AddContactLearnMore.swift` | Help text explaining connection process |
+| `Shared/Views/ChatList/ContactRequestView.swift` | Incoming contact request display |
+| `Shared/Views/ChatList/ContactConnectionView.swift` | Pending connection display |
+| `Shared/Views/ChatList/ContactConnectionInfo.swift` | Connection details sheet |
+| `Shared/Model/SimpleXAPI.swift` | API functions: `apiAddContact`, `apiConnect`, `apiConnectPlan`, `apiAcceptContactRequest`, `apiCreateUserAddress` |
+| `Shared/Model/AppAPITypes.swift` | `ConnectionPlan` enum, `GroupLink` struct |
+| `SimpleXChat/APITypes.swift` | `CreatedConnLink`, `ComposedMessage`, command/response types |
+| `SimpleXChat/ChatTypes.swift` | `Contact`, `PendingContactConnection`, `UserContactRequest` |
+
+## Related Specifications
+
+- `apps/ios/product/README.md` -- Product overview: Contacts capability map
+- `apps/ios/product/flows/messaging.md` -- Messaging after connection is established
diff --git a/apps/ios/product/flows/file-transfer.md b/apps/ios/product/flows/file-transfer.md
new file mode 100644
index 0000000000..0b4b0538cc
--- /dev/null
+++ b/apps/ios/product/flows/file-transfer.md
@@ -0,0 +1,209 @@
+# File Transfer Flow
+
+> **Related spec:** [spec/services/files.md](../../spec/services/files.md)
+
+## Overview
+
+File and media sharing in SimpleX Chat iOS. Small files are sent inline within SMP messages; large files use the XFTP (eXtended File Transfer Protocol) for chunked, encrypted uploads up to 1GB. All files are encrypted end-to-end. Optional local encryption protects downloaded files at rest using AES via `CryptoFile`.
+
+## Prerequisites
+
+- Established contact or group conversation
+- For sending: photo library or file picker access permission
+- For receiving: sufficient device storage
+- XFTP relay servers configured (default servers or custom)
+
+## Size Limits
+
+| Category | Limit | Constant |
+|----------|-------|----------|
+| Inline image (compressed) | 255 KB | `MAX_IMAGE_SIZE` = 261,120 bytes |
+| Auto-receive image | 510 KB | `MAX_IMAGE_SIZE_AUTO_RCV` = MAX_IMAGE_SIZE * 2 |
+| Auto-receive voice | 510 KB | `MAX_VOICE_SIZE_AUTO_RCV` = MAX_IMAGE_SIZE * 2 |
+| Auto-receive video | 1,023 KB | `MAX_VIDEO_SIZE_AUTO_RCV` = 1,047,552 bytes |
+| Max file via XFTP | 1 GB | `MAX_FILE_SIZE_XFTP` = 1,073,741,824 bytes |
+| Max file via SMP | ~8 MB | `MAX_FILE_SIZE_SMP` = 8,000,000 bytes |
+| Max voice message length | 5 min | `MAX_VOICE_MESSAGE_LENGTH` = 300s |
+
+## Step-by-Step Processes
+
+### 1. Send Image
+
+1. User taps the attachment button in `ComposeView` and selects an image.
+2. `ComposeImageView` displays the selected image preview.
+3. Image is compressed to fit within `MAX_IMAGE_SIZE` (255KB).
+4. `ComposedMessage` is built:
+ ```swift
+ ComposedMessage(
+ fileSource: CryptoFile(filePath: compressedImagePath),
+ msgContent: .image(text: captionText, image: base64Thumbnail)
+ )
+ ```
+5. `apiSendMessages(type:id:scope:composedMessages:)` is called.
+6. For images <=255KB: sent inline within the SMP message.
+7. For larger images: XFTP upload is used (see XFTP transfer below).
+8. Recipient auto-receives images up to 510KB (`MAX_IMAGE_SIZE_AUTO_RCV`).
+
+### 2. Send Video
+
+1. User picks a video from the library.
+2. Thumbnail is generated from the first frame.
+3. Video duration is calculated.
+4. `ComposedMessage` is built:
+ ```swift
+ ComposedMessage(
+ fileSource: CryptoFile(filePath: videoFilePath),
+ msgContent: .video(text: captionText, image: base64Thumbnail, duration: durationSeconds)
+ )
+ ```
+5. `apiSendMessages(...)` is called.
+6. Video files are typically >255KB, so XFTP upload is used.
+7. Recipient auto-receives videos up to 1,023KB (`MAX_VIDEO_SIZE_AUTO_RCV`).
+8. `CIVideoView` displays thumbnail with play button; video downloads on tap if not auto-received.
+
+### 3. Send File
+
+1. User taps the attachment button and selects a document via the system file picker.
+2. `ComposeFileView` shows the file name and size.
+3. `ComposedMessage` is built:
+ ```swift
+ ComposedMessage(
+ fileSource: CryptoFile(filePath: filePath),
+ msgContent: .file(fileName)
+ )
+ ```
+4. `apiSendMessages(...)` is called.
+5. If file <=255KB: sent inline via SMP.
+6. If file >255KB and <=1GB: uploaded via XFTP.
+7. Files >1GB: rejected (prevented in UI).
+8. `CIFileView` displays file icon, name, and size for the recipient.
+
+### 4. Send Voice Message
+
+1. User taps and holds the microphone button in `ComposeView`.
+2. `AudioRecPlay` records audio to a temporary file.
+3. `ComposeVoiceView` shows recording waveform and duration.
+4. On release (or tapping stop), recording ends.
+5. Duration is checked against `MAX_VOICE_MESSAGE_LENGTH` (5 minutes / 300 seconds).
+6. `ComposedMessage` is built:
+ ```swift
+ ComposedMessage(
+ fileSource: CryptoFile(filePath: voiceFilePath),
+ msgContent: .voice(text: "", duration: durationSeconds)
+ )
+ ```
+7. `apiSendMessages(...)` is called.
+8. Voice messages <=510KB are sent inline.
+9. Recipient auto-receives voice up to 510KB (`MAX_VOICE_SIZE_AUTO_RCV`).
+10. `CIVoiceView` renders waveform with playback controls.
+
+### 5. Receive File
+
+1. Core receives a message with a file reference via SMP.
+2. `ChatEvent.newChatItems` delivers the chat item with file metadata.
+3. Auto-receive logic checks:
+ - File type and size against auto-receive thresholds.
+ - User's auto-receive preferences.
+4. If auto-received or user taps "Download":
+ ```swift
+ func receiveFile(user: any UserLike, fileId: Int64, userApprovedRelays: Bool = false, auto: Bool = false) async
+ ```
+5. Internally calls `receiveFiles(user:fileIds:userApprovedRelays:auto:)`.
+6. Sends `ChatCommand.receiveFile(fileId:userApprovedRelays:encrypted:inline:)`.
+7. `encrypted` is determined by `privacyEncryptLocalFilesGroupDefault`.
+8. `userApprovedRelays` controls whether unknown XFTP relay servers are trusted.
+9. On success: `ChatResponse2.rcvFileAccepted(user, chatItem)` -- file download begins.
+10. On sender cancelled: `ChatResponse2.rcvFileAcceptedSndCancelled(user, rcvFileTransfer)`.
+11. Download progress is tracked and shown in the UI.
+12. Completed files are stored in the app's `Documents/files/` directory.
+
+### 6. XFTP Transfer (Large Files)
+
+**Upload (sender side):**
+1. File is encrypted locally with a random symmetric key.
+2. Encrypted file is split into chunks.
+3. Chunks are uploaded to one or more XFTP relay servers.
+4. A file description (URI with encryption key and chunk locations) is created.
+5. The file description is sent to the recipient via the SMP message.
+
+**Download (recipient side):**
+1. Recipient receives the file description via SMP.
+2. Chunks are downloaded from XFTP relay servers.
+3. Chunks are reassembled and decrypted locally.
+4. File is available at the local path.
+
+**Standalone file operations** (used for database migration):
+- `uploadStandaloneFile(user:file:ctrl:)` -- upload without a chat message
+- `downloadStandaloneFile(user:url:file:ctrl:)` -- download from a standalone URL
+- `standaloneFileInfo(url:ctrl:)` -- get metadata for a standalone file URL
+
+### 7. Local File Encryption
+
+1. If `privacyEncryptLocalFilesGroupDefault` is enabled in privacy settings:
+ - Downloaded files are encrypted at rest using AES via `CryptoFile`.
+ - `CryptoFile` wraps a file path with encryption metadata.
+2. Encryption key is derived and stored securely.
+3. Files are decrypted on-the-fly when accessed for viewing/playback.
+4. This protects files even if the device storage is accessed externally.
+
+### 8. Unknown Relay Server Approval
+
+1. When receiving a file, XFTP relay servers are checked against known/approved servers.
+2. If unknown servers are detected: `ChatError.error(.fileNotApproved(fileId, unknownServers))`.
+3. If not auto-receiving, user is shown an alert:
+ - "Unknown servers! Without Tor or VPN, your IP address will be visible to these XFTP relays: [server list]."
+ - Option to "Download" (approve) or cancel.
+4. On approval: `receiveFiles(user:fileIds:userApprovedRelays: true)` retries with approval.
+5. If `privacyAskToApproveRelaysGroupDefault` is disabled, relays are auto-approved.
+
+## Data Structures
+
+| Type | Location | Description |
+|------|----------|-------------|
+| `CryptoFile` | `SimpleXChat/CryptoFile.swift` | File path with optional encryption key and nonce for local AES encryption |
+| `MsgContent.image` | `SimpleXChat/ChatTypes.swift` | `.image(text: String, image: String)` -- text caption + base64 thumbnail |
+| `MsgContent.video` | `SimpleXChat/ChatTypes.swift` | `.video(text: String, image: String, duration: Int)` -- caption + thumbnail + duration |
+| `MsgContent.voice` | `SimpleXChat/ChatTypes.swift` | `.voice(text: String, duration: Int)` -- empty text + duration in seconds |
+| `MsgContent.file` | `SimpleXChat/ChatTypes.swift` | `.file(String)` -- file name |
+| `ComposedMessage` | `SimpleXChat/APITypes.swift` | Outgoing message with fileSource, quotedItemId, msgContent, mentions |
+| `FileTransferMeta` | `SimpleXChat/ChatTypes.swift` | Metadata for an ongoing file transfer |
+| `RcvFileTransfer` | `SimpleXChat/ChatTypes.swift` | State of a file being received |
+| `MigrationFileLinkData` | Used for standalone file transfers during database migration |
+
+## Error Cases
+
+| Error | Cause | Handling |
+|-------|-------|----------|
+| `fileNotApproved(fileId, unknownServers)` | Unknown XFTP relay servers | Alert with option to approve and retry |
+| `fileCancelled` | File transfer was cancelled | Silently ignored in `receiveFiles` |
+| `fileAlreadyReceiving` | Duplicate receive request | Silently ignored |
+| `rcvFileAcceptedSndCancelled` | Sender cancelled after acceptance | Alert: "Sender cancelled file transfer" |
+| File too large | Exceeds 1GB XFTP limit | Prevented in UI picker |
+| Network errors | XFTP server unreachable | Standard retry mechanism |
+| Storage full | Insufficient device storage | System-level error |
+
+## Key Files
+
+| File | Purpose |
+|------|---------|
+| `SimpleXChat/FileUtils.swift` | File size constants, path utilities, database file management |
+| `SimpleXChat/CryptoFile.swift` | Local file encryption/decryption with AES |
+| `SimpleXChat/ImageUtils.swift` | Image compression and thumbnail generation |
+| `Shared/Views/Chat/ComposeMessage/ComposeView.swift` | File/media attachment selection and composition |
+| `Shared/Views/Chat/ComposeMessage/ComposeImageView.swift` | Image preview in compose area |
+| `Shared/Views/Chat/ComposeMessage/ComposeFileView.swift` | File preview in compose area |
+| `Shared/Views/Chat/ComposeMessage/ComposeVoiceView.swift` | Voice recording UI with waveform |
+| `Shared/Views/Chat/ChatItem/CIFileView.swift` | File message display: icon, name, size, download action |
+| `Shared/Views/Chat/ChatItem/CIImageView.swift` | Image message display: thumbnail, full-screen tap |
+| `Shared/Views/Chat/ChatItem/CIVideoView.swift` | Video message display: thumbnail, play button, inline playback |
+| `Shared/Views/Chat/ChatItem/CIVoiceView.swift` | Voice message display: waveform, playback controls |
+| `Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift` | Voice message inside a framed (quoted/forwarded) context |
+| `Shared/Views/Chat/ChatItem/FullScreenMediaView.swift` | Full-screen image/video viewer |
+| `Shared/Model/SimpleXAPI.swift` | `apiSendMessages`, `receiveFile`, `receiveFiles`, `uploadStandaloneFile`, `downloadStandaloneFile` |
+| `Shared/Model/AudioRecPlay.swift` | Audio recording and playback engine for voice messages |
+
+## Related Specifications
+
+- `apps/ios/product/README.md` -- Product overview: Messaging capability (file sharing)
+- `apps/ios/product/flows/messaging.md` -- File transfer is part of the message send flow
+- `apps/ios/product/views/chat.md` -- Chat view file/media display
diff --git a/apps/ios/product/flows/group-lifecycle.md b/apps/ios/product/flows/group-lifecycle.md
new file mode 100644
index 0000000000..78d4f28738
--- /dev/null
+++ b/apps/ios/product/flows/group-lifecycle.md
@@ -0,0 +1,216 @@
+# Group Lifecycle Flow
+
+> **Related spec:** [spec/api.md](../../spec/api.md) | [spec/database.md](../../spec/database.md)
+
+## Overview
+
+Complete group management in SimpleX Chat iOS: creating groups, inviting members, joining via links, managing roles and admission, and group deletion. Groups use the same E2E encryption as direct messages -- each member pair has independent encrypted channels. Group metadata (name, image, preferences) is distributed via the group protocol.
+
+## Prerequisites
+
+- User profile created and chat engine running
+- At least one established contact (to invite to a group)
+- For joining via link: a valid group link or invitation
+
+## Step-by-Step Processes
+
+### 1. Create Group
+
+1. User taps "+" in `ChatListView` -> `NewChatMenuButton` -> "Create group".
+2. `AddGroupView` is presented for entering group name, optional image, and description.
+3. User fills in `GroupProfile(displayName:fullName:image:description:)` and taps "Create".
+4. Calls `apiNewGroup(incognito:groupProfile:)`:
+ ```swift
+ func apiNewGroup(incognito: Bool, groupProfile: GroupProfile) throws -> GroupInfo
+ ```
+5. Sends `ChatCommand.apiNewGroup(userId:incognito:groupProfile:)` to core (synchronous).
+6. Core returns `ChatResponse2.groupCreated(user, groupInfo)`.
+7. `GroupInfo` contains the new group's ID, profile, and the creator as owner.
+8. User is navigated to `AddGroupMembersView` to optionally invite contacts.
+9. User can also create a group link at this stage.
+
+### 2. Invite Members
+
+1. From `GroupChatInfoView`, user taps "Add members" -> `AddGroupMembersView`.
+2. `filterMembersToAdd` filters contacts already in the group.
+3. User selects contacts and assigns roles (default: `.member`).
+4. For each selected contact, calls `apiAddMember(groupId:contactId:memberRole:)`:
+ ```swift
+ func apiAddMember(_ groupId: Int64, _ contactId: Int64, _ memberRole: GroupMemberRole) async throws -> GroupMember
+ ```
+5. Core sends group invitation to the contact and returns `ChatResponse2.sentGroupInvitation(user, _, _, member)`.
+6. The invited contact receives a `CIGroupInvitationView` in their chat.
+7. Invited member's status is `.invited` until they accept.
+
+### 3. Join via Link
+
+1. User receives a group link (scanned or pasted).
+2. `apiConnectPlan` validates the link and identifies it as a group link.
+3. For prepared groups (short links): `apiPrepareGroup(connLink:groupShortLinkData:)` shows group info before joining.
+4. `apiConnectPreparedGroup(groupId:incognito:msg:)` or `apiConnect(incognito:connLink:)` initiates joining.
+5. Core processes the join request. Depending on group admission settings:
+ - **Auto-join**: Member is added immediately.
+ - **Approval required**: Member enters pending admission queue.
+6. `apiJoinGroup(groupId:)` is called for invitation-based joins:
+ ```swift
+ func apiJoinGroup(_ groupId: Int64) async throws -> JoinGroupResult?
+ ```
+7. Returns one of:
+ - `.joined(groupInfo:)` -- successfully joined
+ - `.invitationRemoved` -- invitation was revoked (SMP AUTH error)
+ - `.groupNotFound` -- group no longer exists
+
+### 4. Member Admission
+
+1. Group has admission settings configured via `MemberAdmissionView`.
+2. When a new member joins a group requiring approval, admins see pending members.
+3. Admin reviews pending member in the member list.
+4. To accept: `apiAcceptMember(groupId:groupMemberId:memberRole:)`:
+ ```swift
+ func apiAcceptMember(_ groupId: Int64, _ groupMemberId: Int64, _ memberRole: GroupMemberRole) async throws -> (GroupInfo, GroupMember)
+ ```
+5. Core returns `ChatResponse2.memberAccepted(user, groupInfo, member)`.
+6. To reject: remove the pending member (same as member removal).
+7. Member support chat (`MemberSupportView`, `MemberSupportChatToolbar`) allows admins to communicate with pending members.
+
+### 5. Change Member Roles
+
+1. Admin/owner navigates to member info in `GroupChatInfoView`.
+2. Selects new role for the member.
+3. Calls `apiMembersRole(groupId:memberIds:memberRole:)`:
+ ```swift
+ func apiMembersRole(_ groupId: Int64, _ memberIds: [Int64], _ memberRole: GroupMemberRole) async throws -> [GroupMember]
+ ```
+4. Core returns `ChatResponse2.membersRoleUser(user, _, members, _)`.
+5. Available roles (in hierarchy order):
+ - `.owner` -- full control, can delete group
+ - `.admin` -- can manage members, change roles (below admin)
+ - `.moderator` -- can delete messages, moderate content
+ - `.member` -- standard participant, can send messages
+ - `.observer` -- read-only access
+6. Role changes are broadcast to all group members as group events.
+
+### 6. Remove Member
+
+1. Admin/owner navigates to member info -> taps "Remove".
+2. Calls `apiRemoveMembers(groupId:memberIds:withMessages:)`:
+ ```swift
+ func apiRemoveMembers(_ groupId: Int64, _ memberIds: [Int64], _ withMessages: Bool) async throws -> (GroupInfo, [GroupMember])
+ ```
+3. `withMessages: true` also deletes all messages from that member.
+4. Core returns `ChatResponse2.userDeletedMembers(user, updatedGroupInfo, members, withMessages)`.
+5. Removed member receives notification and loses access.
+
+### 7. Block Member for All
+
+1. Admin can block a member's messages from being visible to all group members.
+2. Calls `apiBlockMembersForAll(groupId:memberIds:blocked:)`:
+ ```swift
+ func apiBlockMembersForAll(_ groupId: Int64, _ memberIds: [Int64], _ blocked: Bool) async throws -> [GroupMember]
+ ```
+3. Core returns `ChatResponse2.membersBlockedForAllUser(user, _, members, _)`.
+
+### 8. Leave Group
+
+1. User navigates to `GroupChatInfoView` -> taps "Leave group".
+2. Confirmation dialog is presented.
+3. Calls `leaveGroup(groupId:)` which wraps `apiLeaveGroup(groupId:)`:
+ ```swift
+ func apiLeaveGroup(_ groupId: Int64) async throws -> GroupInfo
+ ```
+4. Core returns `ChatResponse2.leftMemberUser(user, groupInfo)`.
+5. `ChatModel.shared.updateGroup(groupInfo)` updates the UI.
+6. User retains local chat history but can no longer send/receive.
+
+### 9. Delete Group
+
+1. Owner navigates to `GroupChatInfoView` -> taps "Delete group".
+2. Calls `apiDeleteChat(type: .group, id: groupId)`:
+ ```swift
+ func apiDeleteChat(type: ChatType, id: Int64, chatDeleteMode: ChatDeleteMode = .full(notify: true)) async throws
+ ```
+3. Core notifies all members and removes the group.
+4. Chat is removed from `ChatModel.shared.chats`.
+
+### 10. Group Link Management
+
+**Create group link:**
+1. From `GroupLinkView` (accessible via `GroupChatInfoView`).
+2. Calls `apiCreateGroupLink(groupId:memberRole:)`:
+ ```swift
+ func apiCreateGroupLink(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> GroupLink?
+ ```
+3. Returns `GroupLink` containing the link URI and member role.
+4. Optional: `apiAddGroupShortLink(groupId:)` generates an additional short link.
+
+**Update link role:**
+- `apiGroupLinkMemberRole(groupId:memberRole:)` changes the default role for new joiners.
+
+**Delete group link:**
+- `apiDeleteGroupLink(groupId:)` invalidates the link.
+
+**Get existing link:**
+- `apiGetGroupLink(groupId:)` retrieves the current link (returns `nil` if none exists).
+
+### 11. Group Preferences
+
+1. `GroupPreferencesView` allows configuring per-feature preferences.
+2. Features controlled include:
+ - Timed/disappearing messages
+ - Message reactions
+ - Voice messages
+ - File sharing
+ - Direct messages between members
+ - Full message deletion
+ - Message history visibility for new members
+3. Changes are saved via `apiUpdateGroup(groupId:groupProfile:)` with updated preferences.
+4. `GroupWelcomeView` manages the welcome message shown to new joiners.
+
+## Data Structures
+
+| Type | Location | Description |
+|------|----------|-------------|
+| `GroupInfo` | `SimpleXChat/ChatTypes.swift` | Full group model: ID, profile, membership, preferences, business chat info |
+| `GroupProfile` | `SimpleXChat/ChatTypes.swift` | Name, full name, image, description, preferences |
+| `GroupMember` | `SimpleXChat/ChatTypes.swift` | Member model: role, status, profile, connection info |
+| `GroupMemberRole` | `SimpleXChat/ChatTypes.swift` | `.owner`, `.admin`, `.moderator`, `.member`, `.observer` |
+| `GroupMemberStatus` | `SimpleXChat/ChatTypes.swift` | Member lifecycle: `.invited`, `.accepted`, `.connected`, `.complete`, etc. |
+| `GroupLink` | `Shared/Model/AppAPITypes.swift` | Group link with URI, member role, and short link data |
+| `BusinessChatInfo` | `SimpleXChat/ChatTypes.swift` | Business chat metadata for commercial group chats |
+| `JoinGroupResult` | `Shared/Model/SimpleXAPI.swift` | `.joined(groupInfo)`, `.invitationRemoved`, `.groupNotFound` |
+| `GMember` | `Shared/Views/Chat/Group/` | View-layer wrapper around `GroupMember` for list display |
+
+## Error Cases
+
+| Error | Cause | Handling |
+|-------|-------|----------|
+| `errorStore(.groupNotFound)` | Group deleted or not accessible | `JoinGroupResult.groupNotFound` |
+| `errorAgent(.SMP(_, .AUTH))` | Invitation revoked | `JoinGroupResult.invitationRemoved` |
+| `errorStore(.groupLinkNotFound)` | No group link exists | `apiGetGroupLink` returns `nil` |
+| `duplicateGroupLink` | Link already exists for group | Show alert |
+| `errorAgent(.NOTICE(server, preset, expires))` | Server notice during link creation | `showClientNotice` alert |
+| Network errors | SMP/XFTP server unreachable | Retryable via `chatApiSendCmdWithRetry` |
+
+## Key Files
+
+| File | Purpose |
+|------|---------|
+| `Shared/Views/NewChat/AddGroupView.swift` | Group creation UI |
+| `Shared/Views/Chat/Group/AddGroupMembersView.swift` | Member invitation UI |
+| `Shared/Views/Chat/Group/GroupLinkView.swift` | Group link management UI |
+| `Shared/Views/Chat/Group/GroupProfileView.swift` | Group profile editing |
+| `Shared/Views/Chat/Group/GroupPreferencesView.swift` | Feature preferences UI |
+| `Shared/Views/Chat/Group/GroupWelcomeView.swift` | Welcome message editing |
+| `Shared/Views/Chat/Group/MemberAdmissionView.swift` | Admission settings UI |
+| `Shared/Views/Chat/Group/MemberSupportView.swift` | Admin-to-pending-member chat |
+| `Shared/Views/Chat/Group/MemberSupportChatToolbar.swift` | Support chat accept/reject toolbar |
+| `Shared/Views/Chat/Group/SecondaryChatView.swift` | Secondary chat view for member support |
+| `Shared/Model/SimpleXAPI.swift` | All group API functions |
+| `Shared/Model/AppAPITypes.swift` | `GroupLink`, `ConnectionPlan` |
+| `SimpleXChat/ChatTypes.swift` | `GroupInfo`, `GroupProfile`, `GroupMember`, `GroupMemberRole` |
+
+## Related Specifications
+
+- `apps/ios/product/README.md` -- Product overview: Groups capability map
+- `apps/ios/product/flows/connection.md` -- Connection flow (group links use the same connect mechanism)
+- `apps/ios/product/flows/messaging.md` -- Messaging within groups
diff --git a/apps/ios/product/flows/messaging.md b/apps/ios/product/flows/messaging.md
new file mode 100644
index 0000000000..527079995c
--- /dev/null
+++ b/apps/ios/product/flows/messaging.md
@@ -0,0 +1,178 @@
+# Messaging Flow
+
+> **Related spec:** [spec/api.md](../../spec/api.md) | [spec/client/chat-view.md](../../spec/client/chat-view.md) | [spec/client/compose.md](../../spec/client/compose.md)
+
+## Overview
+
+Complete message lifecycle in SimpleX Chat iOS: composing, sending, receiving, editing, deleting, reacting to, replying to, and forwarding messages. All messages are end-to-end encrypted via the SMP protocol. The Haskell core handles encryption, routing, and persistence; the Swift UI layer drives composition and display.
+
+## Prerequisites
+
+- User profile created and chat engine running (`startChat()` completed)
+- At least one established contact or group conversation
+- `ChatModel.shared` populated with chat list data
+
+## Step-by-Step Processes
+
+### 1. Send Text Message
+
+1. User navigates to a conversation (direct or group) via `ChatListView` -> `ChatView`.
+2. User types text into `ComposeView`'s `SendMessageView` text editor.
+3. Link previews are detected and fetched asynchronously (`ComposeLinkView`).
+4. User taps the send button.
+5. `ComposeView` builds a `ComposedMessage`:
+ ```swift
+ ComposedMessage(
+ fileSource: nil,
+ quotedItemId: nil,
+ msgContent: .text("Hello"),
+ mentions: [:]
+ )
+ ```
+6. Calls `apiSendMessages(type:id:scope:live:ttl:composedMessages:)`.
+7. Internally dispatches `ChatCommand.apiSendMessages(...)` to the Haskell core.
+8. Core encrypts, queues via SMP, and returns `ChatResponse1.newChatItems(user, aChatItems)`.
+9. `processSendMessageCmd` extracts `[ChatItem]` from response.
+10. For direct chats, a background task tracks delivery via `chatModel.messageDelivery`.
+11. `ChatModel` updates, UI refreshes to show the new message.
+
+### 2. Send Media (Image/Video/File)
+
+1. User taps the attachment button in `ComposeView`.
+2. **Image**: Picked via `PhotosPicker` or camera. Compressed to <=255KB. Sent inline with `.image(text, base64Image)` content type.
+3. **Video**: Picked from library. Thumbnail generated. Video file sent via XFTP for files >255KB. Content type: `.video(text, thumbnail, duration)`.
+4. **File**: Picked via document picker. If <=255KB, sent inline. If >255KB, uploaded via XFTP (up to 1GB). Content type: `.file(text)`.
+5. `ComposedMessage` includes `fileSource: CryptoFile(filePath:)`.
+6. `apiSendMessages(...)` called with the composed message array.
+7. Core handles XFTP upload for large files (chunked, encrypted upload to XFTP servers).
+8. Recipient receives file reference and can download.
+
+### 3. Receive Message
+
+1. `ChatReceiver.shared` runs `receiveMsgLoop()` continuously calling `chatRecvMsg()`.
+2. Core delivers events via `APIResult`.
+3. On `ChatEvent.newChatItems(user, chatItems)`:
+ - `processReceivedMsg` is called.
+ - For the active user, `ChatModel` is updated with new items.
+ - If the chat is currently open, `ItemsModel` appends to `reversedChatItems`.
+ - `NtfManager` posts a local notification if the app is in the background.
+4. Small files/images attached to incoming messages are auto-received if within size thresholds.
+
+### 4. Edit Message
+
+1. User long-presses a sent message -> selects "Edit" from context menu.
+2. `ComposeView` enters edit mode with the original text pre-filled.
+3. User modifies text and taps send.
+4. Calls `apiUpdateChatItem(type:id:scope:itemId:updatedMessage:live:)`.
+5. Dispatches `ChatCommand.apiUpdateChatItem(...)`.
+6. Core returns `ChatResponse1.chatItemUpdated(user, aChatItem)` or `.chatItemNotChanged(user, aChatItem)`.
+7. `ChatModel` updates the item in place. Edit timestamp is shown in the UI.
+
+### 5. Delete Message
+
+1. User long-presses a message -> selects "Delete".
+2. Presented with options:
+ - **Delete for me** (`CIDeleteMode.cidmInternal`) -- removes locally only.
+ - **Delete for everyone** (`CIDeleteMode.cidmBroadcast`) -- sends deletion to recipient(s).
+3. Calls `apiDeleteChatItems(type:id:scope:itemIds:mode:)`.
+4. Dispatches `ChatCommand.apiDeleteChatItem(type:id:scope:itemIds:mode:)`.
+5. Core returns `ChatResponse1.chatItemsDeleted(user, items, _)` containing `[ChatItemDeletion]`.
+6. For group messages from other members, admin/owner can call `apiDeleteMemberChatItems(groupId:itemIds:)`.
+7. `ChatModel` removes or replaces items with "deleted" placeholders.
+
+### 6. React to Message
+
+1. User long-presses a message -> selects "React" -> picks an emoji.
+2. Calls `apiChatItemReaction(type:id:scope:itemId:add:reaction:)`.
+3. `reaction` is `MsgReaction` (e.g., `.emoji(.heart)`).
+4. `add: true` to add, `add: false` to remove.
+5. Core returns `ChatResponse1.chatItemReaction(user, _, reaction)`.
+6. The reaction is displayed below the message bubble.
+
+### 7. Reply to Message
+
+1. User long-presses a message -> selects "Reply".
+2. `ComposeView` enters reply mode, showing quoted message in `ContextItemView`.
+3. User types reply text and taps send.
+4. `ComposedMessage` is created with `quotedItemId: originalItem.id`.
+5. `apiSendMessages(...)` sends with the quote reference.
+6. Recipient sees the reply with the quoted context rendered above.
+
+### 8. Forward Message
+
+1. User long-presses a message -> selects "Forward".
+2. `ChatItemForwardingView` is presented for destination chat selection.
+3. `apiPlanForwardChatItems(type:id:scope:itemIds:)` validates what can be forwarded, returns `([Int64], ForwardConfirmation?)`.
+4. User confirms and selects destination chat.
+5. Calls `apiForwardChatItems(toChatType:toChatId:toScope:fromChatType:fromChatId:fromScope:itemIds:ttl:)`.
+6. Core returns `ChatResponse1.newChatItems(...)` with the forwarded items in the destination chat.
+
+### 9. Voice Message
+
+1. User taps and holds the microphone button in `ComposeView`.
+2. `AudioRecPlay` starts recording to a temporary file.
+3. On release, recording stops. Duration is calculated (max 5 minutes / 300 seconds).
+4. `ComposedMessage` created with:
+ - `fileSource: CryptoFile` pointing to the audio file
+ - `msgContent: .voice(text: "", duration: seconds)`
+5. `apiSendMessages(...)` sends the voice message.
+6. Voice messages <=510KB sent inline; larger via XFTP.
+7. Recipient sees `CIVoiceView` with waveform and playback controls.
+
+### 10. Delivery Tracking
+
+1. On send, message status starts as `CIStatus.sndNew`.
+2. After SMP delivery: `CIStatus.sndSent(sndProgress)`.
+3. When delivered to recipient's agent: status updates to delivered.
+4. If delivery receipts are enabled by both parties, read status is reported.
+5. Failed delivery results in `CIStatus.sndError*` or `CIStatus.sndWarning*`.
+6. Status is displayed via `CIMetaView` (checkmarks/indicators).
+
+## Data Structures
+
+| Type | Location | Description |
+|------|----------|-------------|
+| `ComposedMessage` | `SimpleXChat/APITypes.swift` | Outgoing message: fileSource, quotedItemId, msgContent, mentions |
+| `MsgContent` | `SimpleXChat/ChatTypes.swift` | Enum: `.text`, `.link`, `.image`, `.video`, `.voice`, `.file` |
+| `CIContent` | `SimpleXChat/ChatTypes.swift` | Chat item content wrapper with sent/received variants |
+| `CIStatus` | `SimpleXChat/ChatTypes.swift` | Delivery status: sndNew, sndSent, sndError, rcvNew, rcvRead |
+| `CIDirection` | `SimpleXChat/ChatTypes.swift` | `.directSnd`, `.directRcv`, `.groupSnd`, `.groupRcv(groupMember)` |
+| `ChatItem` | `SimpleXChat/ChatTypes.swift` | Full message model: content, meta, status, direction, quotedItem |
+| `ChatItemDeletion` | `SimpleXChat/ChatTypes.swift` | Deleted item info with old/new item pairs |
+| `CIDeleteMode` | `SimpleXChat/ChatTypes.swift` | `.cidmInternal` (local) or `.cidmBroadcast` (for everyone) |
+| `MsgReaction` | `SimpleXChat/ChatTypes.swift` | Reaction type (emoji-based) |
+| `UpdatedMessage` | `SimpleXChat/APITypes.swift` | Edited message content for update API |
+
+## Error Cases
+
+| Error | Cause | Handling |
+|-------|-------|----------|
+| `ChatError.errorAgent(.SMP(_, .AUTH))` | Recipient queue issue | Show "Connection error (AUTH)" alert |
+| `ChatError.errorAgent(.BROKER(_, .TIMEOUT))` | Server timeout | Retryable: show retry dialog via `chatApiSendCmdWithRetry` |
+| `ChatError.errorAgent(.BROKER(_, .NETWORK))` | Network failure | Retryable: show retry dialog |
+| Send message error | Core processing failure | `sendMessageErrorAlert` shown to user |
+| `chatItemNotChanged` | Edit with identical content | No error, item returned unchanged |
+| File too large (>1GB) | XFTP limit exceeded | Prevented in UI file picker |
+| `fileNotApproved` | Unknown XFTP relay servers | Show "Unknown servers!" alert with approve option |
+
+## Key Files
+
+| File | Purpose |
+|------|---------|
+| `Shared/Views/Chat/ComposeMessage/ComposeView.swift` | Message composition UI and send logic |
+| `Shared/Views/Chat/ComposeMessage/SendMessageView.swift` | Text input and send button |
+| `Shared/Views/Chat/ComposeMessage/ContextItemView.swift` | Reply/edit context display |
+| `Shared/Views/Chat/ChatItemView.swift` | Per-message rendering dispatcher |
+| `Shared/Views/Chat/ChatItem/MsgContentView.swift` | Text message content with markdown |
+| `Shared/Views/Chat/ChatItem/CIMetaView.swift` | Delivery status indicators |
+| `Shared/Views/Chat/ChatItemForwardingView.swift` | Forward destination picker |
+| `Shared/Views/Chat/ChatItemInfoView.swift` | Message info (delivery details, timestamps) |
+| `Shared/Model/SimpleXAPI.swift` | API functions: `apiSendMessages`, `apiUpdateChatItem`, `apiDeleteChatItems`, `apiChatItemReaction`, `apiForwardChatItems` |
+| `SimpleXChat/APITypes.swift` | `ComposedMessage`, `ChatCommand` enum, response types |
+| `SimpleXChat/ChatTypes.swift` | `MsgContent`, `CIContent`, `CIStatus`, `CIDirection`, `ChatItem` |
+| `Shared/Model/AudioRecPlay.swift` | Voice message recording/playback engine |
+
+## Related Specifications
+
+- `apps/ios/product/views/chat.md` -- Chat view UI specification
+- `apps/ios/product/README.md` -- Product overview and capability map
diff --git a/apps/ios/product/flows/onboarding.md b/apps/ios/product/flows/onboarding.md
new file mode 100644
index 0000000000..5e2e04d42a
--- /dev/null
+++ b/apps/ios/product/flows/onboarding.md
@@ -0,0 +1,239 @@
+# Onboarding Flow
+
+> **Related spec:** [spec/architecture.md](../../spec/architecture.md) | [spec/database.md](../../spec/database.md)
+
+## Overview
+
+First-time setup and migration flows for SimpleX Chat iOS. Covers app initialization, profile creation, server operator selection, notification configuration, and database import/export for device migration. The app uses a Haskell runtime for its core chat engine, with SQLite databases shared between the main app and the Notification Service Extension (NSE).
+
+## Prerequisites
+
+- Fresh install of SimpleX Chat from the App Store, or
+- Existing install with database archive for import/migration
+- iOS 15+ with App Group entitlement configured
+
+## Step-by-Step Processes
+
+### 1. App Initialization Sequence
+
+On every app launch, `SimpleXApp.init()` executes the following in order:
+
+```
+1. haskell_init() -- Start Haskell runtime system (GHC RTS)
+2. UserDefaults.standard.register(defaults:) -- Set default preferences (appDefaults)
+3. setGroupDefaults() -- Configure app group shared defaults
+4. registerGroupDefaults() -- Register group container defaults
+5. setDbContainer() -- Configure database paths in app group container
+6. BGManager.shared.register() -- Register background task handlers
+7. NtfManager.shared.registerCategories() -- Register notification action categories
+```
+
+Then in `ContentView.onAppear`:
+- If no migration is in progress and authentication is set up, `initChatAndMigrate()` is called.
+- This triggers `chatMigrateInit()` to initialize/migrate databases.
+- Then `startChat()` is called to start the chat engine.
+
+### 2. Fresh Install -- Onboarding Steps
+
+Onboarding is managed by `OnboardingStage` enum and `OnboardingView`:
+
+**Step 1: SimpleX Info** (`step1_SimpleXInfo`)
+1. `SimpleXInfo` view is presented.
+2. Explains SimpleX's architecture: no user identifiers, E2E encryption, decentralized servers.
+3. User taps "Create your profile" to proceed.
+
+**Step 2: Create Profile** (`step2_CreateProfile` -- now inline in step 1)
+1. `CreateFirstProfile` view (embedded in the onboarding flow).
+2. User enters display name (required). Full name is set to empty string.
+3. Display name is validated via `mkValidName()` and `canCreateProfile()`.
+4. On "Create":
+ ```swift
+ AppChatState.shared.set(.active)
+ m.currentUser = try apiCreateActiveUser(profile)
+ try startChat()
+ ```
+5. `apiCreateActiveUser(Profile(displayName:fullName:shortDescr:))` creates the user in the Haskell core.
+6. `startChat()` initializes the chat engine.
+7. Onboarding advances to `step3_ChooseServerOperators`.
+
+**Step 3: Choose Server Operators** (`step3_ChooseServerOperators`)
+1. `OnboardingConditionsView` is presented (simplified conditions acceptance).
+2. User reviews and accepts server operator conditions.
+3. This configures which SMP/XFTP server operators to use.
+4. Advances to `step4_SetNotificationsMode`.
+
+**Step 4: Set Notifications** (`step4_SetNotificationsMode`)
+1. `SetNotificationsMode` view is presented.
+2. Three options:
+ - **Instant**: Requires Apple Push Notification service. Registers device token via `apiRegisterToken(token:notificationMode:)`.
+ - **Periodic**: Uses iOS background app refresh. No push token needed.
+ - **Off**: No notifications.
+3. For instant mode: `apiRegisterToken` sends `ChatCommand.apiRegisterToken(token:notificationMode:)` and receives `ChatResponse2.ntfTokenStatus(status)`.
+4. On completion: `onboardingStageDefault.set(.onboardingComplete)`.
+
+**Onboarding Complete** (`onboardingComplete`)
+1. `ChatListView` is shown.
+2. Empty state displays "Add contact" prompt via `ChatHelp`.
+3. If delivery receipts haven't been configured: `chatModel.setDeliveryReceipts = true` triggers a prompt.
+
+### 3. startChat() -- Chat Engine Startup
+
+Called after profile creation or on subsequent app launches:
+
+```swift
+func startChat(refreshInvitations: Bool = true, onboarding: Bool = false) throws {
+ 1. setNetworkConfig(getNetCfg()) -- Apply network configuration
+ 2. apiCheckChatRunning() -- Check if already running
+ 3. listUsers() -- Load all user profiles
+ 4. getUserChatData() -- Load chats, tags, address, TTL
+ 5. NtfManager.shared.setNtfBadgeCount(...) -- Set badge count
+ 6. refreshCallInvitations() -- Check pending call invitations
+ 7. apiGetNtfToken() -- Get notification token status
+ 8. apiStartChat() -- Start the Haskell chat engine
+ 9. registerToken(token:) -- Register push token if available
+ 10. ChatReceiver.shared.start() -- Start message receive loop
+}
+```
+
+### 4. Database Setup
+
+**Location:**
+- App group container (shared with NSE): determined by `dbContainerGroupDefault`
+- Path prefix: `simplex_v1` (`DB_FILE_PREFIX`)
+- Chat database: `simplex_v1_chat.db` (messages, contacts, groups, settings)
+- Agent database: `simplex_v1_agent.db` (SMP connections, encryption keys, queues)
+
+**Initialization:**
+- `chatMigrateInit(useKey:confirmMigrations:backgroundMode:)` in `SimpleXChat/API.swift`.
+- Creates databases if they do not exist.
+- Runs pending migrations with confirmation mode.
+- Handles database encryption:
+ - If keychain storage enabled: generates random DB key on first run (`randomDatabasePassword()`).
+ - Stores key in keychain via `kcDatabasePassword`.
+ - `initialRandomDBPassphraseGroupDefault` tracks whether using auto-generated key.
+
+**Encryption:**
+- Optional database encryption passphrase via `DatabaseEncryptionView`.
+- `apiStorageEncryption(currentKey:newKey:)` changes encryption key.
+- `testStorageEncryption(key:)` validates a key against the database.
+
+### 5. Database Export (Source Device)
+
+1. User navigates to Settings -> Database -> "Export database".
+2. Chat must be stopped first for data consistency.
+3. Calls `apiExportArchive(config: ArchiveConfig)`:
+ ```swift
+ func apiExportArchive(config: ArchiveConfig) async throws -> [ArchiveError]
+ ```
+4. Core creates a ZIP archive containing both databases and file attachments.
+5. Returns any non-fatal `[ArchiveError]` (e.g., file access issues).
+6. User transfers the archive to the new device via AirDrop, file share, etc.
+
+### 6. Database Import (Destination Device)
+
+1. On new device: during onboarding or Settings -> Database -> "Import database".
+2. User selects the archive file.
+3. Calls `apiImportArchive(config: ArchiveConfig)`:
+ ```swift
+ func apiImportArchive(config: ArchiveConfig) async throws -> [ArchiveError]
+ ```
+4. Core extracts the archive, replacing local databases.
+5. Returns any non-fatal `[ArchiveError]`.
+6. Chat engine is restarted with the imported data.
+7. All contacts, groups, messages, and settings are restored.
+
+### 7. In-App Device Migration
+
+An alternative to manual export/import using direct device-to-device transfer.
+
+**Source device** (`MigrateFromDevice` view):
+1. User navigates to Settings -> Database -> "Migrate to another device".
+2. App creates a temporary database and uploads archive via XFTP standalone file.
+3. Generates a migration link containing the file URL and encryption key.
+4. Displays QR code / share link for the destination device.
+
+**Destination device** (`MigrateToDevice` view):
+1. On new device: onboarding detects migration state or user selects "Migrate".
+2. Scans/pastes the migration link.
+3. `downloadStandaloneFile(user:url:file:ctrl:)` downloads the archive from XFTP.
+4. `standaloneFileInfo(url:ctrl:)` validates the file metadata.
+5. Archive is imported, databases are restored.
+6. `chatInitTemporaryDatabase(url:key:confirmation:)` may be used for temporary DB operations during migration.
+7. Chat engine starts with the migrated data.
+
+If migration is interrupted:
+- `chatModel.migrationState` preserves state across app restarts.
+- On next launch, `ContentView.onAppear` detects pending migration and resumes.
+
+### 8. Additional Profile Creation (Multi-Account)
+
+1. From `UserPicker` (profile switcher) -> "Add profile".
+2. `CreateProfile` view is presented (distinct from `CreateFirstProfile`).
+3. User enters display name and optional bio (max 160 bytes JSON-encoded, `MAX_BIO_LENGTH_BYTES`).
+4. `apiCreateActiveUser(profile)` creates additional user.
+5. `listUsers()` and `getUserChatData()` refresh the model.
+6. No onboarding steps -- goes directly to chat list.
+
+## Data Structures
+
+| Type | Location | Description |
+|------|----------|-------------|
+| `OnboardingStage` | `Shared/Views/Onboarding/OnboardingView.swift` | Enum: `step1_SimpleXInfo`, `step2_CreateProfile`, `step3_ChooseServerOperators`, `step4_SetNotificationsMode`, `onboardingComplete` |
+| `Profile` | `SimpleXChat/ChatTypes.swift` | `displayName`, `fullName`, `image`, `shortDescr` |
+| `User` | `SimpleXChat/ChatTypes.swift` | Full user model with profile, userId, and settings |
+| `ArchiveConfig` | `SimpleXChat/APITypes.swift` | Configuration for database export/import |
+| `DBMigrationResult` | `SimpleXChat/API.swift` | Result of database migration: `.ok`, `.errorNotADatabase`, `.errorKeychain`, etc. |
+| `MigrationConfirmation` | `SimpleXChat/API.swift` | Migration confirmation mode: `.error`, `.yesUp`, `.yesUpDown` |
+| `DeviceToken` | `SimpleXChat/ChatTypes.swift` | Apple push notification device token |
+| `NtfTknStatus` | `SimpleXChat/ChatTypes.swift` | Notification token status: registered, active, expired, etc. |
+| `NotificationsMode` | `SimpleXChat/ChatTypes.swift` | `.off`, `.periodic`, `.instant` |
+| `MigrationFileLinkData` | Used in standalone file transfers for device migration |
+| `AppChatState` | `SimpleXChat/` | Shared state: `.active`, `.stopped`, `.suspended` |
+
+## Error Cases
+
+| Error | Cause | Handling |
+|-------|-------|----------|
+| `DBMigrationResult.errorNotADatabase` | Wrong encryption key or corrupt DB | Show `DatabaseErrorView` with options |
+| `DBMigrationResult.errorKeychain` | Keychain access failed | Show error, offer to re-enter passphrase |
+| `DBMigrationResult.errorMigration` | Schema migration failure | Show error with migration details |
+| `duplicateUserError` | Display name already in use | `UserProfileAlert.duplicateUserError` |
+| `invalidDisplayNameError` | Invalid characters in display name | `UserProfileAlert.invalidDisplayNameError` |
+| `createUserError` | Core failed to create user | Alert with error details |
+| `invalidNameError(validName)` | Name needs normalization | Alert suggesting the valid name |
+| Archive import errors | Missing files, version mismatch | Non-fatal `[ArchiveError]` displayed |
+| Migration interrupted | Network failure, app killed | State preserved in `chatModel.migrationState`, resumed on next launch |
+
+## Key Files
+
+| File | Purpose |
+|------|---------|
+| `Shared/SimpleXApp.swift` | App entry point: `haskell_init`, defaults registration, DB container setup, BG tasks |
+| `Shared/AppDelegate.swift` | Push notification registration, URL handling |
+| `Shared/ContentView.swift` | Root view: authentication, onboarding routing, chat initialization |
+| `Shared/Views/Onboarding/OnboardingView.swift` | Onboarding step router, `OnboardingStage` enum |
+| `Shared/Views/Onboarding/SimpleXInfo.swift` | Step 1: Privacy architecture explanation |
+| `Shared/Views/Onboarding/CreateProfile.swift` | Profile creation: `CreateProfile` (additional) and `CreateFirstProfile` (onboarding) |
+| `Shared/Views/Onboarding/ChooseServerOperators.swift` | Step 3: Server operator conditions |
+| `Shared/Views/Onboarding/SetNotificationsMode.swift` | Step 4: Notification mode selection |
+| `Shared/Views/Onboarding/CreateSimpleXAddress.swift` | Optional address creation during onboarding |
+| `Shared/Views/Onboarding/HowItWorks.swift` | Educational content about SimpleX protocol |
+| `Shared/Views/Migration/MigrateFromDevice.swift` | Source device migration UI |
+| `Shared/Views/Migration/MigrateToDevice.swift` | Destination device migration UI |
+| `Shared/Views/Database/DatabaseView.swift` | Database management: export, import, encryption |
+| `Shared/Views/Database/DatabaseEncryptionView.swift` | Database passphrase management |
+| `Shared/Views/Database/DatabaseErrorView.swift` | Database error recovery UI |
+| `Shared/Views/Database/MigrateToAppGroupView.swift` | Legacy migration from Documents to App Group container |
+| `Shared/Model/SimpleXAPI.swift` | `startChat`, `apiCreateActiveUser`, `apiExportArchive`, `apiImportArchive`, `apiRegisterToken` |
+| `SimpleXChat/API.swift` | `chatMigrateInit`, `chatInitTemporaryDatabase`, low-level DB initialization |
+| `SimpleXChat/FileUtils.swift` | DB file paths, constants (`DB_FILE_PREFIX`, `CHAT_DB`, `AGENT_DB`) |
+| `SimpleXChat/AppGroup.swift` | App group container configuration |
+| `SimpleXChat/KeyChain.swift` | Keychain access for DB passphrase and app passwords |
+| `Shared/Model/BGManager.swift` | Background task registration and scheduling |
+| `Shared/Model/NtfManager.swift` | Notification management and badge counts |
+
+## Related Specifications
+
+- `apps/ios/product/README.md` -- Product overview: architecture and capabilities
+- `apps/ios/product/flows/connection.md` -- After onboarding, user establishes first connections
+- `apps/ios/product/flows/messaging.md` -- Messaging starts after profile creation
diff --git a/apps/ios/product/gaps.md b/apps/ios/product/gaps.md
new file mode 100644
index 0000000000..04cf97a6a7
--- /dev/null
+++ b/apps/ios/product/gaps.md
@@ -0,0 +1,61 @@
+# SimpleX Chat iOS -- Known Gaps & Recommendations
+
+> Aggregation of `[GAP]` and `[REC]` annotations discovered during specification analysis. Organized by product area.
+>
+> **Related spec:** [spec/README.md](../spec/README.md)
+
+---
+
+## UI: Error Feedback
+
+### GAP: No user-visible error on FFI command failure
+**Source:** [spec/architecture.md](../spec/architecture.md)
+API calls via `chatApiSendCmd` return `APIResult` which can be `.error(ChatError)`. Not all error cases surface user-visible feedback in the UI.
+
+**REC:** Audit all `chatApiSendCmd` call sites and ensure `.error` cases show appropriate alerts or banners.
+
+---
+
+## UI: Loading States
+
+### GAP: No loading indicator during initial chat list population
+**Source:** [spec/client/chat-list.md](../spec/client/chat-list.md)
+When `ChatModel.chatInitialized` transitions to `true`, the chat list appears fully formed. There is no intermediate loading state for users with large numbers of chats.
+
+**REC:** Add a progress indicator during `apiGetChats` for users with 100+ conversations.
+
+---
+
+## Flows: Group Lifecycle
+
+### GAP: Bulk member role change — API supports batch but UI uses single-member calls
+**Source:** [spec/api.md](../spec/api.md)
+`APIMembersRole` accepts `NonEmpty GroupMemberId`, supporting batch role changes at the API level. However, the iOS UI (`GroupMemberInfoView.swift`) currently invokes it with a single member at a time.
+
+**REC:** Expose batch role change in the UI for group admins managing large groups.
+
+---
+
+## Security
+
+### GAP: Database passphrase not enforced by default
+**Source:** [spec/database.md](../spec/database.md)
+Database encryption is optional and requires the user to manually set a passphrase. New installations start with an unencrypted database.
+
+**REC:** Consider prompting users to set a database passphrase during onboarding, especially on devices without hardware encryption.
+
+### GAP: No forward secrecy indicator in UI
+**Source:** [product/glossary.md](glossary.md)
+While the double-ratchet protocol provides forward secrecy, there is no UI indicator showing whether a specific conversation has achieved forward secrecy (i.e., completed initial key exchange ratcheting).
+
+**REC:** Add a security indicator in contact/group info showing ratchet state.
+
+---
+
+## Documentation
+
+### GAP: Haskell Store layer not fully specified
+**Source:** [spec/database.md](../spec/database.md)
+The Haskell Store modules (`Store/Direct.hs`, `Store/Groups.hs`, `Store/Messages.hs`, etc.) are referenced by function name but not fully specified with parameter types and return types.
+
+**REC:** Expand database spec with key Store function signatures as the specification matures.
diff --git a/apps/ios/product/glossary.md b/apps/ios/product/glossary.md
new file mode 100644
index 0000000000..0353c8f606
--- /dev/null
+++ b/apps/ios/product/glossary.md
@@ -0,0 +1,235 @@
+# SimpleX Chat iOS -- Glossary
+
+> SimpleX Chat iOS domain glossary. Defines all domain terms used in SimpleX Chat with links to relevant specifications and source code.
+>
+> **Related spec:** [spec/api.md](../spec/api.md) | [spec/architecture.md](../spec/architecture.md)
+
+## Table of Contents
+
+1. [Protocols & Cryptography](#protocols--cryptography)
+2. [Core Data Types](#core-data-types)
+3. [Commands & Events](#commands--events)
+4. [Connection & Identity](#connection--identity)
+5. [Messaging Features](#messaging-features)
+6. [Calling & Media](#calling--media)
+7. [Notifications & Background](#notifications--background)
+8. [Application Architecture](#application-architecture)
+9. [Configuration & Preferences](#configuration--preferences)
+
+---
+
+## Protocols & Cryptography
+
+### SMP (Simplex Messaging Protocol)
+The core messaging protocol used for asynchronous message delivery through relay servers. Each conversation uses separate unidirectional queues, and sender and receiver queues have no shared identifier. Defined in the [simplexmq](https://github.com/simplex-chat/simplexmq) library. *See: protocol spec `simplexmq/protocol/simplex-messaging.md`, implementation `simplexmq/src/Simplex/Messaging/Protocol.hs`*
+
+### SMP Server
+A relay server that stores and forwards encrypted messages between parties. Users can configure custom SMP servers or use defaults. Servers cannot see message contents or correlate senders with receivers. *See: `Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift`*
+
+### XFTP (eXtended File Transfer Protocol)
+A protocol for transferring large files (up to 1GB) through relay servers. Files are encrypted, split into chunks, and uploaded to XFTP servers. Recipients download and reassemble chunks independently. Defined in the [simplexmq](https://github.com/simplex-chat/simplexmq) library. *See: protocol spec `simplexmq/protocol/xftp.md`, implementation `simplexmq/src/Simplex/FileTransfer/Protocol.hs`; chat-level integration `../../src/Simplex/Chat/Files.hs`*
+
+### XFTP Server
+A relay server that stores encrypted file chunks for asynchronous file transfer. Like SMP servers, users can configure custom XFTP servers. *See: `Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift`*
+
+### SMP Agent
+The lower-level agent library (in [simplexmq](https://github.com/simplex-chat/simplexmq)) that manages SMP connections, queue creation/rotation, duplex connection establishment, message delivery, and the double-ratchet encryption protocol. The chat application layer communicates with the agent via its functional API. *See: protocol spec `simplexmq/protocol/agent-protocol.md`, implementation `simplexmq/src/Simplex/Messaging/Agent.hs`; chat-level integration `../../src/Simplex/Chat/Controller.hs`*
+
+### Double Ratchet
+The key agreement protocol used for E2E encryption. Provides forward secrecy and break-in recovery by deriving new encryption keys for each message. Based on the Signal protocol's double-ratchet algorithm, augmented with post-quantum KEM (PQDR). Implemented in the [simplexmq](https://github.com/simplex-chat/simplexmq) library. *See: protocol spec `simplexmq/protocol/pqdr.md`, implementation `simplexmq/src/Simplex/Messaging/Crypto/Ratchet.hs`*
+
+### Post-Quantum Encryption
+Optional quantum-resistant key exchange (PQ) available for direct chats. Uses a hybrid scheme combining classical X25519 with Streamlined NTRU-Prime 761 (sntrup761) KEM. The hybrid secret is SHA3-256(DH_secret || KEM_shared_secret). Implemented in the [simplexmq](https://github.com/simplex-chat/simplexmq) library. *See: protocol spec `simplexmq/protocol/pqdr.md`, implementation `simplexmq/src/Simplex/Messaging/Crypto/SNTRUP761.hs`; Swift types `SimpleXChat/ChatTypes.swift` (PQEncryption, PQSupport)*
+
+### E2E Encryption
+End-to-end encryption ensuring that only the communicating parties can read message contents. Neither SMP relay servers nor any network observer can decrypt messages. All SimpleX Chat messages are E2E encrypted by default using the double-ratchet protocol. *See: `simplexmq/src/Simplex/Messaging/Crypto/Ratchet.hs` (ratchet implementation), `simplexmq/src/Simplex/Messaging/Agent/Protocol.hs` (E2E message envelopes)*
+
+### Forward Secrecy
+A property of the double-ratchet protocol ensuring that compromise of current encryption keys does not compromise past session keys. Each message uses a derived key that is deleted after use. *See: `simplexmq/protocol/pqdr.md`, `simplexmq/src/Simplex/Messaging/Crypto/Ratchet.hs`*
+
+### Chat Protocol (x-events)
+The chat-level protocol defining message envelopes and content types exchanged between chat participants. Includes x-events (XMsgNew, XMsgUpdate, XMsgDel, XCallInv, XFileCancel, XGrpMemNew, etc.), MsgContent (text, image, video, voice, file, link), and message encoding (Binary/JSON). This is distinct from the lower-level SMP transport protocol. *See: `../../src/Simplex/Chat/Protocol.hs`*
+
+### Security Code
+A hash of the shared encryption session displayed as a numeric code and QR code. Contacts can compare security codes out-of-band to verify they have an uncompromised E2E session. *See: `Shared/Views/Chat/VerifyCodeView.swift`, `../../src/Simplex/Chat/Controller.hs` (APIVerifyContact)*
+
+---
+
+## Core Data Types
+
+### ChatItem
+The fundamental unit of content in a conversation. Represents a single message, event, call record, or system notification within a chat. Each ChatItem has direction (sent/received), content, metadata, and optional quoted context. *See: `../../src/Simplex/Chat/Messages.hs` (data ChatItem), `SimpleXChat/ChatTypes.swift`*
+
+### ChatInfo
+A type-safe wrapper identifying a conversation and its metadata. Variants: DirectChat (1:1 with Contact), GroupChat (with GroupInfo), LocalChat (note folder), ContactRequest, ContactConnection. *See: `../../src/Simplex/Chat/Messages.hs` (data ChatInfo), `SimpleXChat/ChatTypes.swift`*
+
+### CIContent
+The content payload of a ChatItem. Differentiates sent vs. received content types: message content (text/image/file/voice/link), deletion markers, call records, group events, and feature preference changes. *See: `../../src/Simplex/Chat/Messages/CIContent.hs` (data CIContent)*
+
+### User
+A local user profile within the app. Each user has an independent set of contacts, groups, and connections. Multiple users can exist in one app installation. Fields include userId, profile, display name, and optional view password hash for hidden profiles. *See: `../../src/Simplex/Chat/Types.hs` (data User), `Shared/Model/ChatModel.swift`*
+
+### Contact
+A remote party with whom the user has an established E2E encrypted connection. Stores the contact's profile, local alias, connection status, feature preferences, and UI settings. *See: `../../src/Simplex/Chat/Types.hs` (data Contact), `SimpleXChat/ChatTypes.swift`*
+
+### GroupInfo
+Metadata for a group conversation including group profile, member count, preferences, and membership status. Contains the user's own membership record as a GroupMember. *See: `../../src/Simplex/Chat/Types.hs` (data GroupInfo)*
+
+### GroupMember
+A participant in a group conversation. Each member has a role, status, profile, and optionally a direct connection. The user's own membership is also represented as a GroupMember within GroupInfo. *See: `../../src/Simplex/Chat/Types.hs` (data GroupMember)*
+
+### Connection
+A low-level SMP agent connection between two parties. Each connection has a status (new, joined, ready, deleted), an agent connection ID, and is associated with a specific contact or group member. *See: `../../src/Simplex/Chat/Types.hs` (data Connection)*
+
+### ConnStatus
+The lifecycle state of a Connection: ConnNew (created, awaiting join), ConnJoined (joined, handshake in progress), ConnReady (fully established), ConnDeleted (terminated). *See: `../../src/Simplex/Chat/Types.hs` (data ConnStatus)*
+
+### ContactStatus
+The status of a contact record: CSActive (normal), CSDeleted (deleted by contact), CSDeletedByUser (deleted by user). *See: `../../src/Simplex/Chat/Types.hs` (data ContactStatus)*
+
+### GroupMemberRole
+Hierarchical role assigned to a group member. From most to least privileged: GROwner, GRAdmin, GRModerator, GRMember, GRObserver. Roles determine permissions for sending messages, managing members, and moderating content. *See: `../../src/Simplex/Chat/Types/Shared.hs` (data GroupMemberRole)*
+
+### GroupMemberStatus
+The lifecycle state of a group member: GSMemRejected, GSMemRemoved, GSMemLeft, GSMemGroupDeleted, GSMemUnknown, GSMemInvited, GSMemIntroduced, GSMemIntroInvited, GSMemAccepted, GSMemAnnounced, GSMemConnected, GSMemComplete, GSMemCreator, GSMemPendingReview, GSMemPendingApproval. *See: `../../src/Simplex/Chat/Types.hs` (data GroupMemberStatus)*
+
+### FileTransfer
+Represents an in-progress or completed file transfer. Variants: FTSnd (sending, with metadata and per-recipient transfer records) and FTRcv (receiving). Tracks protocol (SMP inline or XFTP), progress, and encryption parameters. *See: `../../src/Simplex/Chat/Types.hs` (data FileTransfer)*
+
+### ChatTag
+A user-defined label for organizing conversations in the chat list. Each tag has a text label and optional emoji. Chats can have multiple tags, and the chat list can be filtered by tag. *See: `../../src/Simplex/Chat/Types.hs` (data ChatTag), `Shared/Views/ChatList/TagListView.swift`*
+
+---
+
+## Commands & Events
+
+### ChatCommand
+A sum type representing all commands the UI can send to the chat controller. Examples: APISendMessages, APIGetChat, APIConnect, APINewGroup, APIDeleteChatItem. Commands are serialized and dispatched through the FFI bridge. *See: `../../src/Simplex/Chat/Controller.hs` (data ChatCommand)*
+
+### ChatResponse
+A sum type representing synchronous responses from the chat controller to the UI after processing a ChatCommand. Examples: CRActiveUser, CRNewChatItems, CRChatItemUpdated. *See: `../../src/Simplex/Chat/Controller.hs` (data ChatResponse)*
+
+### ChatEvent
+A sum type representing asynchronous events pushed from the chat controller to the UI. These are unsolicited notifications about state changes: incoming messages, connection status changes, call invitations, etc. *See: `../../src/Simplex/Chat/Controller.hs` (data ChatEvent)*
+
+### ChatError
+Error types returned by the chat controller. Variants: ChatError (application-level), ChatErrorAgent (SMP agent errors), ChatErrorStore (database errors), ChatErrorRemoteHost (remote desktop errors). *See: `../../src/Simplex/Chat/Controller.hs` (data ChatError)*
+
+---
+
+## Connection & Identity
+
+### SimpleX Address
+A long-lived contact address that others can use to send connection requests. Unlike one-time invitation links, an address can be reused by multiple contacts. The user can accept or reject each incoming request. *See: `Shared/Views/UserSettings/UserAddressView.swift`, `../../src/Simplex/Chat/Controller.hs` (APICreateMyAddress)*
+
+### Contact Link
+A one-time or reusable URI that initiates a contact connection. When scanned or opened, it triggers the SMP handshake to establish an E2E encrypted channel between two parties. *See: `Shared/Views/NewChat/NewChatView.swift`*
+
+### Group Link
+A shareable URI that allows new members to join a group. The link connects to the group host, who then introduces the new member to existing members. Configurable with a default member role. *See: `Shared/Views/Chat/Group/GroupLinkView.swift`, `../../src/Simplex/Chat/Types.hs` (data GroupLink)*
+
+### Short Link
+A compact version of SimpleX contact or group links, using a shorter URI format for easier sharing. Contains encoded connection parameters with reduced character length. *See: `../../src/Simplex/Chat/Controller.hs`*
+
+### Incognito Mode
+A privacy feature that generates a random profile (display name and avatar) for each new contact connection. The real user profile is never shared with incognito contacts. Can be toggled per-connection at invitation time. *See: `Shared/Views/UserSettings/IncognitoHelp.swift`, `../../src/Simplex/Chat/ProfileGenerator.hs`*
+
+### Hidden Profile
+A user profile protected by a separate password. Hidden profiles do not appear in the user picker or profile list. To access a hidden profile, the user enters its password in the search field of the user picker. *See: `Shared/Views/UserSettings/HiddenProfileView.swift`, `../../src/Simplex/Chat/Controller.hs` (APIHideUser)*
+
+---
+
+## Messaging Features
+
+### Delivery Receipt
+A confirmation that a message was successfully delivered to the recipient's device. Displayed as a double-check indicator on sent messages. Can be enabled or disabled per contact or globally. *See: `Shared/Views/UserSettings/SetDeliveryReceiptsView.swift`, `../../src/Simplex/Chat/Controller.hs`*
+
+### Read Receipt
+An indicator that a recipient has viewed a received message. Currently not implemented as a separate feature; delivery receipts serve as the primary delivery confirmation. *See: `Shared/Views/UserSettings/PrivacySettings.swift`*
+
+### Timed Message
+A message with a configurable time-to-live (TTL). After the TTL expires, the message is automatically deleted from both sender and recipient devices. The TTL is set as a chat feature preference. Also referred to as a disappearing message. *See: `../../src/Simplex/Chat/Types/Preferences.hs` (TimedMessagesPreference)*
+
+### Disappearing Message
+Synonym for Timed Message. A message that self-destructs after a configured duration. The timer starts when the message is read by the recipient. *See: `../../src/Simplex/Chat/Types/Preferences.hs` (TimedMessagesPreference)*
+
+### Message Integrity
+Verification that messages are received in order and without gaps. The system detects skipped messages and decryption failures, displaying integrity error indicators in the chat. *See: `Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift`, `../../src/Simplex/Chat/Messages/CIContent.hs`*
+
+### Decryption Error
+An error occurring when a received message cannot be decrypted, typically due to ratchet synchronization issues. The UI displays a specific error view with recovery options. *See: `Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift`, `../../src/Simplex/Chat/Messages/CIContent.hs`*
+
+---
+
+## Calling & Media
+
+### CallKit
+Apple's framework for integrating VoIP calls with the native iOS call UI. SimpleX Chat uses CallKit to display incoming calls on the lock screen, support call answering from the system UI, and manage audio sessions. *See: `Shared/Views/Call/CallController.swift`, `Shared/Views/Call/CallManager.swift`*
+
+### WebRTC
+The real-time communication framework used for audio/video calls. SimpleX Chat wraps WebRTC in an E2E encrypted layer, with signaling performed through the existing SMP message channel rather than a central server. *See: `Shared/Views/Call/WebRTC.swift`, `Shared/Views/Call/WebRTCClient.swift`*
+
+### ICE Server
+An Interactive Connectivity Establishment server used by WebRTC to discover network paths between call participants. SimpleX Chat supports configuring custom ICE servers. *See: `Shared/Views/UserSettings/RTCServers.swift`, `SimpleXChat/CallTypes.swift`*
+
+### TURN Server
+A Traversal Using Relays around NAT server that relays WebRTC media when direct peer-to-peer connection is not possible. A specific type of ICE server. SimpleX Chat allows configuring custom TURN servers for call relay. *See: `Shared/Views/UserSettings/RTCServers.swift`*
+
+### RcvCallInvitation
+An in-memory data structure representing an incoming call invitation. Contains the calling contact, call type (audio/video), encryption keys, and shared key for the WebRTC session. Not persisted to database. *See: `../../src/Simplex/Chat/Call.hs` (data RcvCallInvitation)*
+
+---
+
+## Notifications & Background
+
+### Notification Service Extension (NSE)
+An iOS app extension that processes incoming push notifications while the main app is not running. The NSE starts a temporary chat controller, decrypts the incoming message, and displays a notification with the message preview. *See: `SimpleX NSE/NotificationService.swift`, `SimpleX NSE/NSEAPITypes.swift`*
+
+### Background Task
+An iOS background execution context used for periodic message fetching when instant notifications are not enabled. Managed by BGManager to check for new messages at system-determined intervals. *See: `Shared/Model/BGManager.swift`*
+
+---
+
+## Application Architecture
+
+### chat_ctrl
+The opaque C pointer to the Haskell chat controller, obtained via FFI initialization. All chat operations are dispatched through this controller handle. The main app and NSE maintain separate chat_ctrl instances. *See: `SimpleXChat/API.swift` (chatController, getChatCtrl)*
+
+### ComposeState
+A Swift struct holding the current state of the message composition area. Tracks the message text, parsed markdown, preview, attached media, editing context, quote context, and voice recording state. *See: `Shared/Views/Chat/ComposeMessage/ComposeView.swift` (struct ComposeState)*
+
+### ChatModel
+The central observable model object for the iOS app. Holds all reactive state: current user, chat list, active chat, call state, app preferences, and navigation state. Published properties drive SwiftUI view updates. *See: `Shared/Model/ChatModel.swift` (class ChatModel)*
+
+### ItemsModel
+An observable model managing the list of ChatItems displayed in a conversation view. Handles item loading, pagination, merging of new items, and secondary chat filtering. *See: `Shared/Model/ChatModel.swift` (class ItemsModel)*
+
+### AppTheme
+An observable object encapsulating the current visual theme: name, base theme, color overrides, app-specific colors, and wallpaper configuration. Shared as an environment object across the SwiftUI view hierarchy. *See: `Shared/Theme/Theme.swift` (class AppTheme)*
+
+---
+
+## Configuration & Preferences
+
+### FeaturePreference
+A type class (Haskell) / protocol pattern representing a user's preference for a specific chat feature (e.g., timed messages, voice messages, calls). Each preference has an allow/enable setting and optional parameters. Feature preferences are negotiated between contacts. *See: `../../src/Simplex/Chat/Types/Preferences.hs` (class FeatureI, type FeaturePreference)*
+
+### ChatSettings
+Per-chat configuration including notification mode (all/mentions/off), send receipts toggle, favorite flag, and tag assignments. Stored per contact and per group. *See: `../../src/Simplex/Chat/Types.hs` (data ChatSettings)*
+
+### UserDefaults / GroupDefaults
+iOS persistent key-value storage for app preferences. GroupDefaults (UserDefaults with the app group suite name) is shared between the main app and the NSE extension. Stores settings like notification mode, appearance preferences, and runtime flags. *See: `SimpleXChat/AppGroup.swift` (groupDefaults)*
+
+---
+
+## Cross-References
+
+- Product overview: [README.md](README.md)
+- Concept index: [concepts.md](concepts.md)
+- Haskell core types: `../../src/Simplex/Chat/Types.hs`
+- Haskell controller: `../../src/Simplex/Chat/Controller.hs`
+- Haskell chat protocol (x-events): `../../src/Simplex/Chat/Protocol.hs`
+- Haskell messages: `../../src/Simplex/Chat/Messages.hs`
+- Swift model: `Shared/Model/ChatModel.swift`
+- Swift API types: `SimpleXChat/APITypes.swift`, `SimpleXChat/ChatTypes.swift`
+- simplexmq library (SMP, XFTP, Agent, encryption): [github.com/simplex-chat/simplexmq](https://github.com/simplex-chat/simplexmq)
diff --git a/apps/ios/product/rules.md b/apps/ios/product/rules.md
new file mode 100644
index 0000000000..b41792898b
--- /dev/null
+++ b/apps/ios/product/rules.md
@@ -0,0 +1,119 @@
+# SimpleX Chat iOS -- Business Rules
+
+> Business invariants enforced by the SimpleX Chat iOS app and Haskell core. Each rule states the invariant, where it is enforced, and links to the relevant spec.
+>
+> **Related spec:** [spec/api.md](../spec/api.md) | [spec/architecture.md](../spec/architecture.md) | [spec/state.md](../spec/state.md)
+
+---
+
+## Security & Privacy
+
+### RULE-01: No user identifiers
+**Rule:** The system MUST NOT assign, generate, or expose any persistent user identifier (phone number, email, username, UUID) that could be used to correlate a user across conversations.
+**Enforced by:** SMP protocol design in simplexmq library; each connection uses independent unidirectional queues with no shared identifier.
+**Spec:** [spec/architecture.md](../spec/architecture.md)
+
+### RULE-02: End-to-end encryption on all messages
+**Rule:** All message content MUST be encrypted end-to-end using double-ratchet (with optional post-quantum KEM). The SMP server MUST NOT have access to plaintext.
+**Enforced by:** simplexmq library (`Simplex.Messaging.Crypto.Ratchet`); encryption happens before `chat_send_cmd_retry` FFI call.
+**Spec:** [spec/architecture.md](../spec/architecture.md)
+
+### RULE-03: Database encryption at rest
+**Rule:** Both SQLite databases (chat and agent) MUST be encrypted with SQLCipher when the user sets a database passphrase.
+**Enforced by:** `chat_migrate_init_key` in Haskell core via SQLCipher; `DatabaseEncryptionView.swift` in UI.
+**Spec:** [spec/database.md](../spec/database.md)
+
+### RULE-04: Local authentication before content access
+**Rule:** When app lock is enabled, the app MUST authenticate the user (Face ID, Touch ID, or passcode) before displaying any chat content.
+**Enforced by:** `LocalAuthView.swift`, `ContentView.swift` (`contentViewAccessAuthenticated` guard on `ChatModel`).
+**Spec:** [spec/architecture.md](../spec/architecture.md)
+
+### RULE-05: Incognito profiles are per-connection
+**Rule:** When incognito mode is used for a connection, the generated random profile MUST be unique to that connection and MUST NOT be reused across connections.
+**Enforced by:** `ProfileGenerator.hs` generates fresh profile per connection; stored on the connection entity.
+**Spec:** [spec/api.md](../spec/api.md)
+
+---
+
+## Message Integrity
+
+### RULE-06: Message order preservation
+**Rule:** Messages within a single connection MUST be displayed in the order determined by the SMP agent's sequence numbers, not by local timestamps.
+**Enforced by:** `Store/Messages.hs` (`createNewChatItem` uses agent-assigned ordering); `ItemsModel` in `ChatModel.swift` preserves this order.
+**Spec:** [spec/state.md](../spec/state.md)
+
+### RULE-07: Edited messages retain history
+**Rule:** When a message is edited, the previous version MUST be preserved in `chat_item_versions` and accessible via the item info view.
+**Enforced by:** `Controller.hs` (`APIUpdateChatItem`); `Store/Messages.hs` (`updateChatItem` creates version record); `ChatItemInfoView.swift` displays history.
+**Spec:** [spec/api.md](../spec/api.md)
+
+### RULE-08: Deleted messages respect deletion mode
+**Rule:** `CIDeleteMode.cidmBroadcast` sends deletion to recipient; `cidmInternal` only deletes locally. Moderation deletion (`cidmInternalMark`) marks the item but retains a placeholder.
+**Enforced by:** `Controller.hs` (`APIDeleteChatItem` checks `CIDeleteMode`); `MarkedDeletedItemView.swift` renders moderation placeholders.
+**Spec:** [spec/api.md](../spec/api.md)
+
+### RULE-09: Timed messages auto-delete after TTL
+**Rule:** Messages with a TTL MUST be automatically deleted from local storage after the configured time-to-live expires.
+**Enforced by:** `Controller.hs` (background task scheduling); `Store/Messages.hs` (TTL-based cleanup).
+**Spec:** [spec/api.md](../spec/api.md)
+
+---
+
+## Group Integrity
+
+### RULE-10: Role hierarchy enforcement
+**Rule:** A member can only modify members with strictly lower roles. Owner > Admin > Moderator > Member > Observer.
+**Enforced by:** `Controller.hs` (`APIMembersRole` validates role hierarchy); `GroupMemberInfoView.swift` restricts available actions in UI.
+**Spec:** [spec/api.md](../spec/api.md)
+
+### RULE-11: Group creator is always owner
+**Rule:** The user who creates a group MUST be assigned the `GROwner` role and cannot be demoted.
+**Enforced by:** `Controller.hs` (`APINewGroup`); `Store/Groups.hs` (`createNewGroup` assigns owner role).
+**Spec:** [spec/api.md](../spec/api.md)
+
+### RULE-12: Group link role assignment
+**Rule:** Members joining via group link MUST receive the role configured on the link (default: `GRMember`). Only admins and owners can create group links.
+**Enforced by:** `Controller.hs` (`APICreateGroupLink` takes `memberRole` parameter); `GroupLinkView.swift` UI restricts to admin+.
+**Spec:** [spec/api.md](../spec/api.md)
+
+---
+
+## File Transfer
+
+### RULE-13: File size limits
+**Rule:** Files up to 1GB are transferred via XFTP. The system MUST reject files exceeding the configured maximum.
+**Enforced by:** Haskell core (`Files.hs` checks file size); XFTP protocol enforces chunk limits.
+**Spec:** [spec/services/files.md](../spec/services/files.md)
+
+### RULE-14: File encryption at rest
+**Rule:** When `privacyEncryptLocalFiles` is enabled, downloaded files MUST be encrypted locally using AES with per-file random key/nonce stored in `CryptoFile`.
+**Enforced by:** `CryptoFile.swift` (`encryptCryptoFile`, `decryptCryptoFile`); `Library/Commands.hs` uses `CryptoFileArgs` for file encryption.
+**Spec:** [spec/services/files.md](../spec/services/files.md)
+
+---
+
+## Notification Delivery
+
+### RULE-15: Notification preview respects privacy setting
+**Rule:** Notification content MUST respect `NotificationPreviewMode`: `.message` shows full content, `.contact` shows sender only, `.hidden` shows generic alert.
+**Enforced by:** `Notifications.swift` (notification content creation checks `ntfPreviewModeGroupDefault`); `NotificationService.swift` (NSE content generation).
+**Spec:** [spec/services/notifications.md](../spec/services/notifications.md)
+
+### RULE-16: NSE database coordination
+**Rule:** The NSE and main app MUST NOT write to the database simultaneously. File locks coordinate access.
+**Enforced by:** `chat_close_store` / `chat_reopen_store` FFI calls; NSE uses short-lived database sessions.
+**Spec:** [spec/architecture.md](../spec/architecture.md)
+
+---
+
+## Call Integrity
+
+### RULE-17: Call encryption key exchange
+**Rule:** WebRTC call encryption keys MUST be negotiated over the existing E2E encrypted SMP channel, not through any external signaling server.
+**Enforced by:** `ActiveCallView.swift` sends call signaling via `apiSendCallInvitation`/`apiSendCallAnswer` which use SMP; `Call.hs` defines call protocol.
+**Spec:** [spec/services/calls.md](../spec/services/calls.md)
+
+### RULE-18: CallKit region restriction
+**Rule:** CallKit MUST be disabled in regions where it is restricted (China). The app uses in-app call UI as fallback.
+**Enforced by:** `CallController.swift` checks `useCallKit()` based on region; `ActiveCallView.swift` provides fallback UI.
+**Spec:** [spec/services/calls.md](../spec/services/calls.md)
diff --git a/apps/ios/product/views/call.md b/apps/ios/product/views/call.md
new file mode 100644
index 0000000000..f32f7ec243
--- /dev/null
+++ b/apps/ios/product/views/call.md
@@ -0,0 +1,122 @@
+# Audio / Video Call
+
+> **Related spec:** [spec/services/calls.md](../../spec/services/calls.md)
+
+## Purpose
+
+Make and receive end-to-end encrypted audio and video calls over WebRTC. Supports CallKit integration for native iOS call UI, picture-in-picture for video calls, audio device selection, and collapsible call overlay.
+
+## Route / Navigation
+
+- **Entry point (outgoing)**: Tap audio or video call button in `ChatInfoView` action buttons or `ChatView` toolbar
+- **Entry point (incoming)**: `IncomingCallView` banner appears at top of screen; or native CallKit UI if enabled
+- **Presented by**: `ActiveCallView` is overlaid on the main app view when `chatModel.activeCall` is set
+- **Collapsible**: Call view can be collapsed via `chatModel.activeCallViewIsCollapsed` to return to chat while call continues
+- **Dismiss**: Call ends when user taps end button or remote party disconnects
+
+## Page Sections
+
+### Incoming Call Banner (`IncomingCallView`)
+
+Displayed as an overlay banner when `CallController.activeCallInvitation` is set:
+
+| Element | Description |
+|---|---|
+| Profile avatar | User profile image (shown when multiple profiles exist) |
+| Call type icon | `video.fill` (green) for video calls, `phone.fill` (green) for audio |
+| Call type text | "Audio call" or "Video call" with caller info |
+| Caller profile | `ProfilePreview` showing caller name and image |
+| Reject button | Red `phone.down.fill` icon -- ends the invitation |
+| Ignore button | Neutral `multiply` icon -- dismisses the banner without rejecting |
+| Accept button | Green `checkmark` icon -- accepts the call; if another call is active, ends it first |
+
+Sound: Ringtone plays via `SoundPlayer.startRingtone()` while banner is visible (unless call view is already showing).
+
+### Active Call View (`ActiveCallView`)
+
+Full-screen overlay with black background:
+
+| Element | Description |
+|---|---|
+| Remote video | Full-screen `CallViewRemote` showing remote party's camera feed; tap toggles between `scaleAspectFill` and `scaleAspectFit` |
+| Local video preview | Small floating `CallViewLocal` in top-right corner (30% width); shows local camera with rounded corners |
+| Call overlay | `ActiveCallOverlay` with call controls (hidden when PiP is active for video calls) |
+| Screen keep-on | `AppDelegate.keepScreenOn(true)` prevents screen dimming during calls |
+
+### Call Controls (`ActiveCallOverlay`)
+
+Bottom bar of the active call:
+
+| Control | Description |
+|---|---|
+| Mute toggle | Microphone on/off |
+| Speaker toggle | Speaker/receiver switch |
+| Camera switch | Front/back camera toggle (video calls) |
+| Video toggle | Enable/disable video during call |
+| End call | Red phone-down button to terminate |
+| Audio device picker | `AudioDevicePicker` / `CallAudioDeviceManager` for selecting output (receiver, speaker, Bluetooth, AirPods) |
+
+### Picture-in-Picture (PiP)
+
+- When `pipShown == true` and call has video, the call overlay is hidden
+- PiP window shows the remote video feed
+- User can interact with the app normally while call continues
+
+### CallKit Integration
+
+Managed by `CallController`:
+
+| Feature | Description |
+|---|---|
+| Native incoming call UI | iOS system call screen for incoming calls (when CallKit is enabled) |
+| Call history | Optionally shown in Phone app recents (`DEFAULT_CALL_KIT_CALLS_IN_RECENTS`) |
+| System audio routing | CallKit manages audio session configuration |
+| Lock screen answering | Call can be answered from lock screen via system UI |
+
+When CallKit is not used, the app falls back to `IncomingCallView` banner.
+
+### WebRTC Client
+
+| Component | Description |
+|---|---|
+| `WebRTCClient` | Manages peer connection, ICE candidates, media tracks |
+| `WebRTC.swift` | Bridge between native code and WebRTC JavaScript via `WKWebView` |
+| `CallViewRenderers` | `CallViewLocal` and `CallViewRemote` SwiftUI wrappers for video renderers |
+
+## Loading / Error States
+
+| State | Behavior |
+|---|---|
+| Permissions required | Prompts for microphone (and camera for video) permissions on first call |
+| Connecting | Call overlay shows connecting state; `SoundPlayer` plays connecting tone |
+| WebRTC client creation | `createWebRTCClient()` called on appear and when `canConnectCall` changes |
+| Call ended | `CallSoundsPlayer.vibrate(long: true)` on disconnect if was connected; audio session reset to `.soloAmbient` |
+| Call failed | Call dismissed; WebRTC client cleaned up |
+| No call invitation | `IncomingCallView` body is empty when no active invitation |
+
+## Audio Session Management
+
+- During call: Audio session configured for voice chat
+- Camera permissions: `AVFoundation.AVCaptureDevice` authorization checked
+- Audio device management: `CallAudioDeviceManager` handles routing changes and device enumeration
+- Post-call cleanup: Audio session reverted to `.soloAmbient`
+
+## Related Specs
+
+- `spec/services/calls.md` -- Call service specification
+- [Chat](chat.md) -- Call buttons in chat navigation bar
+- [Contact Info](contact-info.md) -- Call buttons in contact info action row
+- [Settings](settings.md) -- Call settings (CallKit, ICE servers, relay policy)
+
+## Source Files
+
+- `Shared/Views/Call/ActiveCallView.swift` -- Main active call view with video renderers and overlay
+- `Shared/Views/Call/IncomingCallView.swift` -- Incoming call notification banner
+- `Shared/Views/Call/CallController.swift` -- CallKit integration and call lifecycle management
+- `Shared/Views/Call/CallManager.swift` -- Call state management and CXProvider delegate
+- `Shared/Views/Call/CallAudioDeviceManager.swift` -- Audio device enumeration and routing
+- `Shared/Views/Call/AudioDevicePicker.swift` -- Audio output device picker UI
+- `Shared/Views/Call/WebRTC.swift` -- WebRTC signaling bridge via WKWebView
+- `Shared/Views/Call/WebRTCClient.swift` -- WebRTC peer connection management
+- `Shared/Views/Call/CallViewRenderers.swift` -- SwiftUI wrappers for local and remote video views
+- `Shared/Views/Call/SoundPlayer.swift` -- Ringtone and call sound playback
diff --git a/apps/ios/product/views/chat-list.md b/apps/ios/product/views/chat-list.md
new file mode 100644
index 0000000000..6c2d868d64
--- /dev/null
+++ b/apps/ios/product/views/chat-list.md
@@ -0,0 +1,113 @@
+# Chat List (Home Screen)
+
+> **Related spec:** [spec/client/chat-list.md](../../spec/client/chat-list.md)
+
+## Purpose
+
+Main screen of the SimpleX Chat app. Displays all conversations sorted by last activity, serves as the navigation root, and provides access to user profiles, settings, and new chat creation.
+
+## Route / Navigation
+
+- **Entry point**: App launch (root view), or back-navigation from any chat
+- **Presented by**: `ContentView` as the default view when `chatModel.chatId == nil`
+- **Navigation stack**: `NavStackCompat` wrapping `chatListView` with destination `chatView`
+- **UserPicker sheet**: Triggered by tapping the user avatar in the toolbar; presents `UserPicker` as a custom sheet, which links to `UserPickerSheetView` sub-sheets (address, preferences, profiles, current profile, use from desktop, settings)
+
+## Page Sections
+
+### Toolbar
+
+| Element | Location | Behavior |
+|---|---|---|
+| User avatar button | Leading | Opens `UserPicker` sheet (profile switcher, address, settings, preferences, connect to desktop) |
+| Connection status indicator | Center (`SubsStatusIndicator`) | Shows server subscription status; taps navigate to `ServersSummaryView` |
+| New chat button (pencil icon) | Trailing | Opens `NewChatSheet` modal |
+
+The toolbar supports two layout modes:
+- **Standard (top)**: Navigation bar with `.topBarLeading`, `.principal`, `.topBarTrailing` placements
+- **One-hand UI (bottom)**: Toolbar items placed in `.bottomBar` with the list vertically flipped via `scaleEffect(y: -1)`
+
+### Search Bar
+
+- Text field with magnifying glass icon
+- When active, `searchMode = true` hides the navigation bar and shows inline search
+- Filters chat list in real-time by contact/group name and message content
+- Detects pasted SimpleX links (`searchShowingSimplexLink`) and offers to connect
+
+### Chat Filter Tabs (Tags)
+
+Managed by `ChatTagsModel` and `TagListView`:
+
+| Filter | PresetTag | Description |
+|---|---|---|
+| All | (none) | No filter, shows all chats |
+| Unread | `.unread` | Chats with unread messages |
+| Favorites | `.favorites` | User-favorited chats |
+| Groups | `.groups` | Group conversations only |
+| Contacts | `.contacts` | Direct contacts only |
+| Business | `.business` | Business chat conversations |
+| Notes | `.notes` | Notes to self |
+| Group Reports | `.groupReports` | Moderation reports (non-collapsible) |
+| Custom tags | `.userTag(ChatTag)` | User-created tags with custom names |
+
+### Chat Preview Rows
+
+Each row rendered by `ChatPreviewView` inside `ChatListNavLink`:
+
+| Element | Description |
+|---|---|
+| Avatar | Profile image or colored initials circle; online status indicator for contacts |
+| Chat name | Display name (contact, group, or note-to-self) |
+| Last message preview | Truncated text of most recent message; supports markdown rendering |
+| Timestamp | Relative time of last activity (e.g., "2m", "1h", "Yesterday") |
+| Unread badge | Numeric count badge for unread messages; distinct styling for mentions |
+| Muted indicator | Bell-slash icon when notifications are muted |
+| Pinned indicator | Pin icon for pinned chats |
+| Incognito indicator | Shows when connected via incognito profile |
+| Connection status | Shows connecting/pending state for incomplete connections |
+
+### Swipe Actions
+
+- **Trailing swipe**: Mute/unmute, pin/unpin, tag management
+- **Leading swipe**: Mark as read/unread
+- **Context menu** (long press): Full set of actions including delete, clear chat, toggle favorite
+
+### Floating Elements
+
+- **One-hand UI card** (`OneHandUICard`): Dismissible card shown to introduce bottom toolbar mode
+- **Address creation card** (`AddressCreationCard`): Prompts user to create a SimpleX address
+
+### Pull-to-Refresh
+
+Triggers `reconnectAllServers()` after user confirmation alert ("Reconnect servers?"). Uses additional traffic to force message delivery.
+
+## Loading / Error States
+
+| State | Behavior |
+|---|---|
+| Chat database not started | Settings row shows exclamation icon; chat running == false disables interactions |
+| No chats | `ChatHelp` view displayed with onboarding guidance |
+| Connection in progress | `ConnectProgressManager` overlay with connecting text |
+| Search with no results | Empty list with no special empty-state view |
+
+## Related Specs
+
+- `spec/client/chat-list.md` -- Chat list feature specification
+- `spec/state.md` -- Application state management
+- [User Profiles](user-profiles.md) -- Profile switching from UserPicker
+- [Settings](settings.md) -- Settings accessed via UserPicker
+- [New Chat](new-chat.md) -- New chat sheet triggered from toolbar
+- [Chat](chat.md) -- Navigated to when tapping a chat row
+
+## Source Files
+
+- `Shared/Views/ChatList/ChatListView.swift` -- Main view, toolbar, search, filter logic
+- `Shared/Views/ChatList/ChatPreviewView.swift` -- Individual chat row rendering
+- `Shared/Views/ChatList/ChatListNavLink.swift` -- Navigation link wrapper with swipe actions
+- `Shared/Views/ChatList/TagListView.swift` -- Filter tab bar (preset + custom tags)
+- `Shared/Views/ChatList/UserPicker.swift` -- User profile picker sheet
+- `Shared/Views/ChatList/ChatHelp.swift` -- Empty-state help view
+- `Shared/Views/ChatList/ContactRequestView.swift` -- Contact request row rendering
+- `Shared/Views/ChatList/ContactConnectionView.swift` -- Pending connection row rendering
+- `Shared/Views/ChatList/OneHandUICard.swift` -- One-hand UI introduction card
+- `Shared/Views/ChatList/ServersSummaryView.swift` -- Server subscription summary
diff --git a/apps/ios/product/views/chat.md b/apps/ios/product/views/chat.md
new file mode 100644
index 0000000000..57202846eb
--- /dev/null
+++ b/apps/ios/product/views/chat.md
@@ -0,0 +1,165 @@
+# Chat View (Conversation)
+
+> **Related spec:** [spec/client/chat-view.md](../../spec/client/chat-view.md) | [spec/client/compose.md](../../spec/client/compose.md)
+
+## Purpose
+
+Full conversation view for displaying and interacting with messages in a direct contact chat, group chat, or note-to-self. Supports text messaging with markdown, media attachments, voice messages, E2E encrypted calls, message reactions, replies, forwarding, and content search/filtering.
+
+## Route / Navigation
+
+- **Entry point**: Tap a chat row in `ChatListView`
+- **Presented by**: `NavStackCompat` destination from `ChatListView`, bound to `chatModel.chatId`
+- **Back navigation**: Dismiss sets `chatModel.chatId = nil`, returning to chat list
+- **Sub-navigation**: Info button navigates to `ChatInfoView` (contact) or `GroupChatInfoView` (group); member avatars navigate to `GroupMemberInfoView`
+
+## Page Sections
+
+### Navigation Bar
+
+Custom toolbar overlaying the chat with themed material background:
+
+| Element | Description |
+|---|---|
+| Back button | Returns to chat list |
+| Contact/Group avatar | Small profile image |
+| Chat name | Display name; tappable to open info sheet |
+| Encryption badge | Shows PQ (post-quantum) or standard E2E status |
+| Call buttons | Audio and video call icons (direct chats only) |
+| Search button | Toggles in-chat message search |
+| Info button | Opens `ChatInfoView` or `GroupChatInfoView` |
+
+### Message List
+
+Rendered by `EndlessScrollView` with lazy loading and pagination:
+
+| Feature | Description |
+|---|---|
+| Scroll direction | Bottom-to-top (newest messages at bottom) |
+| Pagination | Loads more items on scroll to top (`loadingTopItems`) and bottom (`loadingBottomItems`) |
+| Merged items | Adjacent messages from the same sender are visually merged via `MergedItems` |
+| Floating buttons | Scroll-to-bottom button with unread count; scroll-to-first-unread button |
+| Date separators | Sticky date headers between messages from different days |
+| Wallpaper | Themed background image with tint and opacity from `theme.wallpaper` |
+| Content filter | Filter messages by type: `.images`, `.files`, `.links` |
+
+### Message Types
+
+Each type has a dedicated view in `Shared/Views/Chat/ChatItem/`:
+
+| Type | View | Description |
+|---|---|---|
+| Text | `MsgContentView` | Rendered with markdown (bold, italic, code, links, mentions) |
+| Image | `CIImageView` | Thumbnail with tap-to-fullscreen via `FullScreenMediaView` |
+| Video | `CIVideoView` | Video thumbnail with play button; inline playback |
+| Voice | `CIVoiceView` / `FramedCIVoiceView` | Waveform visualization with playback controls and duration |
+| File | `CIFileView` | File icon, name, size; download/open actions |
+| Link preview | `CILinkView` | URL preview card with title, description, image |
+| Emoji-only | `EmojiItemView` | Large emoji rendering without message bubble |
+| Call event | `CICallItemView` | Call status (missed, ended, duration) |
+| Group event | `CIEventView` | Member joined/left, role changes, group updates |
+| E2EE info | `CIChatFeatureView` | Encryption status and feature change notifications |
+| Group invitation | `CIGroupInvitationView` | Inline group join invitation card |
+| Deleted | `DeletedItemView` / `MarkedDeletedItemView` | Placeholder for deleted messages |
+| Decryption error | `CIRcvDecryptionError` | Error with ratchet sync suggestion |
+| Invalid JSON | `CIInvalidJSONView` | Developer fallback for malformed items |
+| Integrity error | `IntegrityErrorItemView` | Message integrity/gap warnings |
+
+### Message Interactions
+
+Long-press context menu on any message:
+
+| Action | Description |
+|---|---|
+| Reply | Sets compose bar to reply mode with quoted message |
+| Forward | Opens `forwardedChatItems` sheet to pick destination chat |
+| Copy | Copies message text to clipboard |
+| Edit | Enters edit mode in compose bar (own messages, within edit window) |
+| Delete | Delete for self or delete for everyone (with confirmation) |
+| React | Opens emoji reaction picker |
+| Select multiple | Enters multi-select mode (`selectedChatItems`) with bulk delete/forward |
+| Info | Shows delivery status and timestamps |
+
+Emoji reactions bar displayed below messages with reaction counts.
+
+### Compose Bar (`ComposeView`)
+
+| Element | Description |
+|---|---|
+| Text input | `NativeTextEditor` with markdown support and auto-growing height |
+| Attachment button | Opens picker for images, videos, files, camera |
+| Send button | Sends composed message; changes to voice record button when empty |
+| Voice record | Hold-to-record with waveform preview; swipe-to-cancel |
+| Reply quote | Shows quoted message above input when replying |
+| Edit indicator | Shows "editing" label when editing a previous message |
+| Link preview | Auto-generated preview card for detected URLs (`ComposeLinkView`) |
+| Image/Video preview | Thumbnail strip for selected media (`ComposeImageView`) |
+| File preview | File name and size for attached file (`ComposeFileView`) |
+| Voice preview | Waveform of recorded voice message (`ComposeVoiceView`) |
+| Live message | Real-time typing broadcast (optional, with alert on first use) |
+| Context actions | `ContextContactRequestActionsView` for accepting/rejecting contact requests; `ContextPendingMemberActionsView` for pending group member actions |
+| Commands menu | `CommandsMenuView` for bot/menu commands in chats with `menuCommands` |
+| Group mentions | `GroupMentionsView` autocomplete popup when typing `@` in groups |
+| Profile picker | `ContextProfilePickerView` for choosing incognito/main profile |
+
+### Member Support Chat (Groups)
+
+For groups with member support enabled:
+- `MemberSupportView` and `MemberSupportChatToolbar` shown as secondary chat within group
+- `SecondaryChatView` for scoped group chat views (reports, member support)
+- User knocking state: `userMemberKnockingTitleBar()` shown when user is pending admission
+
+## Loading / Error States
+
+| State | Behavior |
+|---|---|
+| Initial load | Messages load from `ItemsModel` with merged items; `allowLoadMoreItems` throttles pagination |
+| Loading more (top) | `loadingTopItems` spinner at top of scroll view |
+| Loading more (bottom) | `loadingBottomItems` spinner at bottom |
+| Connection in progress | `ConnectProgressManager` shows connecting text below compose bar |
+| Connecting text | "connecting..." label shown below message list when chat not yet ready |
+| Send disabled | Compose bar shows `disabledText` reason when `userCantSendReason` is set |
+| Empty chat | No messages placeholder (implicit -- empty scroll view) |
+
+## Related Specs
+
+- `spec/client/chat-view.md` -- Chat view feature specification
+- `spec/client/compose.md` -- Compose bar specification
+- [Chat List](chat-list.md) -- Parent navigation
+- [Contact Info](contact-info.md) -- Info sheet for direct chats
+- [Group Info](group-info.md) -- Info sheet for group chats
+- [Call](call.md) -- Audio/video calls initiated from toolbar
+
+## Source Files
+
+- `Shared/Views/Chat/ChatView.swift` -- Main chat view, message list, navigation, state management
+- `Shared/Views/Chat/ChatItemView.swift` -- Individual message item rendering dispatcher
+- `Shared/Views/Chat/ComposeMessage/ComposeView.swift` -- Compose bar container
+- `Shared/Views/Chat/ComposeMessage/SendMessageView.swift` -- Send button and voice record
+- `Shared/Views/Chat/ComposeMessage/NativeTextEditor.swift` -- Text input with markdown
+- `Shared/Views/Chat/ComposeMessage/ComposeImageView.swift` -- Image attachment preview
+- `Shared/Views/Chat/ComposeMessage/ComposeFileView.swift` -- File attachment preview
+- `Shared/Views/Chat/ComposeMessage/ComposeVoiceView.swift` -- Voice recording preview
+- `Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift` -- Link preview generation
+- `Shared/Views/Chat/ComposeMessage/ContextItemView.swift` -- Reply/edit context display
+- `Shared/Views/Chat/ComposeMessage/ContextContactRequestActionsView.swift` -- Contact request accept/reject
+- `Shared/Views/Chat/ComposeMessage/ContextPendingMemberActionsView.swift` -- Pending member actions
+- `Shared/Views/Chat/ComposeMessage/ContextProfilePickerView.swift` -- Profile picker for incognito
+- `Shared/Views/Chat/ChatItem/FramedItemView.swift` -- Framed message bubble rendering
+- `Shared/Views/Chat/ChatItem/MsgContentView.swift` -- Text message content with markdown
+- `Shared/Views/Chat/ChatItem/CIImageView.swift` -- Image message view
+- `Shared/Views/Chat/ChatItem/CIVideoView.swift` -- Video message view
+- `Shared/Views/Chat/ChatItem/CIVoiceView.swift` -- Voice message view
+- `Shared/Views/Chat/ChatItem/CIFileView.swift` -- File message view
+- `Shared/Views/Chat/ChatItem/CILinkView.swift` -- Link preview view
+- `Shared/Views/Chat/ChatItem/EmojiItemView.swift` -- Large emoji view
+- `Shared/Views/Chat/ChatItem/CICallItemView.swift` -- Call event view
+- `Shared/Views/Chat/ChatItem/CIEventView.swift` -- Group/system event view
+- `Shared/Views/Chat/ChatItem/CIChatFeatureView.swift` -- Feature change notification
+- `Shared/Views/Chat/ChatItem/CIMetaView.swift` -- Timestamp and delivery status
+- `Shared/Views/Chat/ChatItem/FullScreenMediaView.swift` -- Fullscreen image/video viewer
+- `Shared/Views/Chat/ChatItem/AnimatedImageView.swift` -- Animated GIF rendering
+- `Shared/Views/Chat/Group/GroupMentions.swift` -- @mention autocomplete
+- `Shared/Views/Chat/Group/MemberSupportView.swift` -- Member support scoped chat
+- `Shared/Views/Chat/Group/MemberSupportChatToolbar.swift` -- Support chat toolbar
+- `Shared/Views/Chat/Group/SecondaryChatView.swift` -- Secondary scoped chat view
diff --git a/apps/ios/product/views/contact-info.md b/apps/ios/product/views/contact-info.md
new file mode 100644
index 0000000000..5223bfcae4
--- /dev/null
+++ b/apps/ios/product/views/contact-info.md
@@ -0,0 +1,154 @@
+# Contact Info
+
+> **Related spec:** [spec/client/chat-view.md](../../spec/client/chat-view.md)
+
+## Purpose
+
+View contact details, manage per-contact preferences, verify security codes for E2E encryption, manage connection settings, and perform destructive actions like blocking or deleting a contact.
+
+## Route / Navigation
+
+- **Entry point**: Tap the info button in `ChatView` navigation bar (when viewing a direct contact chat)
+- **Presented by**: `NavigationView` sheet from `ChatView` via `showChatInfoSheet`
+- **Sub-navigation**:
+ - Contact preferences -> `ContactPreferencesView`
+ - Security code verification -> `VerifyCodeView`
+ - Chat wallpaper -> `ChatWallpaperEditorSheet`
+
+## Page Sections
+
+### Contact Info Header
+
+| Element | Description |
+|---|---|
+| Profile image | Large circular avatar; tappable |
+| Display name | Contact's display name |
+| Full name | Optional full name below display name |
+| Connection status | Shows if contact is ready, connecting, or has issues |
+
+### Local Alias
+
+Editable text field (`aliasTextFieldFocused`) for setting a local-only name visible only on this device. Not shared with the contact.
+
+### Action Buttons
+
+Horizontal row of quick-action buttons (width divided by 4):
+
+| Button | Description |
+|---|---|
+| Search | Triggers `onSearch` to search messages in chat |
+| Audio call | Initiate audio call (`AudioCallButton`) |
+| Video call | Initiate video call (`VideoButton`) |
+| Mute/Unmute | Toggle notification mode (`nextNtfMode`) |
+
+Call buttons check `connectionStats` and show alerts if connection state prevents calling.
+
+### Incognito Section
+
+Shown only when `customUserProfile` is set (connected via incognito):
+
+| Element | Description |
+|---|---|
+| "Your random profile" label | Shows the incognito display name used for this contact |
+
+### Connection Settings Section
+
+| Element | Condition | Description |
+|---|---|---|
+| Verify security code | `connectionCode` available | Navigate to `VerifyCodeView` for QR-based code verification |
+| Contact preferences | Always | Navigate to `ContactPreferencesView` |
+| Send receipts | Always | Toggle: yes / no / default(yes) / default(no) |
+| Synchronize connection | `ratchetSyncAllowed` | Fix encryption ratchet desynchronization |
+| Chat theme | Always | Navigate to `ChatWallpaperEditorSheet` |
+
+All items disabled when `!contact.ready || !contact.active`.
+
+### Chat TTL Section
+
+| Element | Description |
+|---|---|
+| Chat TTL option | `ChatTTLOption` -- auto-delete timer for messages on this device |
+
+Footer: "Delete chat messages from your device."
+
+### Encryption Info Section
+
+Shown when `contact.activeConn` exists:
+
+| Element | Description |
+|---|---|
+| E2E encryption | "Quantum resistant" (PQ enabled) or "Standard" |
+
+### Contact Address Section
+
+Shown when `contact.contactLink` exists:
+
+| Element | Description |
+|---|---|
+| QR code | `SimpleXLinkQRCode` displaying the contact's address |
+| Share address | Share button for the contact's SimpleX address link |
+
+Footer: "You can share this address with your contacts to let them connect with **[name]**."
+
+### Servers Section
+
+Shown when `contact.ready && contact.active`:
+
+| Element | Description |
+|---|---|
+| Subscription status | `SubStatusRow` showing connection health; tappable for details |
+| Change receiving address | Button to switch SMP receiving queue (disabled during switch) |
+| Abort changing address | Button to cancel in-progress address switch |
+| Receiving via | SMP server hostnames for receiving queues |
+| Sending via | SMP server hostnames for sending queues |
+
+### Danger Zone Section
+
+| Action | Description |
+|---|---|
+| Clear chat | Delete all messages locally (confirmation alert) |
+| Delete contact | Remove contact entirely (confirmation alert) |
+
+### Developer Section
+
+Shown when `developerTools` is enabled:
+
+| Element | Description |
+|---|---|
+| Local name | Internal local display name |
+| Database ID | API entity ID |
+| Debug delivery | Button to fetch queue info via `apiContactQueueInfo` |
+
+## Loading / Error States
+
+| State | Behavior |
+|---|---|
+| Loading connection info | `apiContactInfo` and `apiGetContactCode` called on appear; stats and code populated asynchronously |
+| Progress indicator | `ProgressView` overlay during TTL changes |
+| Contact not ready | Settings section disabled with reduced opacity |
+| Contact inactive | Settings section disabled |
+| Errors | Alert with localized error title and message |
+
+## Alerts
+
+| Alert | Trigger |
+|---|---|
+| `clearChatAlert` | Tap clear chat |
+| `subStatusAlert` | Tap subscription status row |
+| `switchAddressAlert` | Tap change receiving address |
+| `abortSwitchAddressAlert` | Tap abort address change |
+| `syncConnectionForceAlert` | Force ratchet sync |
+| `queueInfo` | Debug delivery results |
+| `someAlert` | Various sub-component alerts |
+
+## Related Specs
+
+- `spec/api.md` -- Contact API commands (info, code verification, preferences, delete)
+- [Chat](chat.md) -- Parent chat view
+- [Group Info](group-info.md) -- Similar pattern for group info
+
+## Source Files
+
+- `Shared/Views/Chat/ChatInfoView.swift` -- Main contact info view with all sections
+- `Shared/Views/Chat/ContactPreferencesView.swift` -- Per-contact feature preferences (timed messages, reactions, voice, calls, file transfer, full delete)
+- `Shared/Views/Chat/VerifyCodeView.swift` -- Security code verification via QR scan or visual comparison
diff --git a/apps/ios/product/views/group-info.md b/apps/ios/product/views/group-info.md
new file mode 100644
index 0000000000..9291b3ed2f
--- /dev/null
+++ b/apps/ios/product/views/group-info.md
@@ -0,0 +1,147 @@
+# Group Chat Info
+
+> **Related spec:** [spec/client/chat-view.md](../../spec/client/chat-view.md)
+
+## Purpose
+
+View and manage group settings, member list, group preferences, group links, member admission, welcome messages, and moderation features. The scope of available actions depends on the user's role within the group (member, moderator, admin, owner).
+
+## Route / Navigation
+
+- **Entry point**: Tap the info button in `ChatView` navigation bar (when viewing a group chat)
+- **Presented by**: `NavigationView` sheet from `ChatView` via `showChatInfoSheet`
+- **Sub-navigation**:
+ - Edit group profile -> `GroupProfileView`
+ - Add members -> `AddGroupMembersView`
+ - Group link -> `GroupLinkView`
+ - Group preferences -> `GroupPreferencesView` (via `GroupPreferencesButton`)
+ - Welcome message -> `GroupWelcomeView`
+ - Member info -> `GroupMemberInfoView`
+ - Chat wallpaper -> `ChatWallpaperEditorSheet`
+ - Member support -> `MemberSupportView`
+ - Group reports -> `GroupReportsChatNavLink`
+
+## Page Sections
+
+### Group Info Header
+
+| Element | Description |
+|---|---|
+| Group image | Large circular profile image |
+| Group name | Display name (editable by owners) |
+| Member count | "N members" label |
+| Full name | Optional secondary name |
+| Description | Group description text (if set) |
+
+### Local Alias
+
+Editable text field for a local-only alias (not shared with other members). Focused via `aliasTextFieldFocused`.
+
+### Action Buttons
+
+Horizontal row of action buttons:
+
+| Button | Description |
+|---|---|
+| Search | Triggers `onSearch` callback to search messages in chat |
+| Mute/Unmute | Toggle notification mode (`nextNtfMode`) |
+
+### Group Management Section
+
+| Element | Condition | Description |
+|---|---|---|
+| Group link | `canAddMembers` and not business chat | Navigate to `GroupLinkView` to create/manage invitation link |
+| Member support | Not business chat, role >= moderator | Navigate to member support chat view |
+| Group reports | `canModerate` | Navigate to group reports chat |
+| User support chat | Member active, role < moderator or has support chat | Navigate to own support chat with moderators |
+
+### Group Profile Section
+
+| Element | Condition | Description |
+|---|---|---|
+| Edit group | Owner, not business chat | Navigate to `GroupProfileView` for editing name, image, description |
+| Welcome message | Has description or is owner (not business) | Navigate to `GroupWelcomeView` for add/edit |
+| Group preferences | Always | Navigate to `GroupPreferencesView` -- timed messages, reactions, voice, files, direct messages, history visibility |
+
+Footer: "Only group owners can change group preferences." (or "Only chat owners can change preferences." for business chats)
+
+### Chat Settings Section
+
+| Element | Description |
+|---|---|
+| Send receipts | Toggle delivery receipts; disabled for groups > 20 current members with explanation |
+| Chat theme | Navigate to `ChatWallpaperEditorSheet` |
+| Chat TTL | `ChatTTLOption` -- set auto-deletion timer for messages on device |
+
+Footer: "Delete chat messages from your device."
+
+### Member List Section
+
+Header shows total member count (e.g., "25 members").
+
+| Element | Description |
+|---|---|
+| Invite members button | Shown if `canAddMembers`; disabled with tap alert if incognito |
+| Search field | Filter members by name (`searchText`) |
+| Member rows | Each shows: avatar, display name, role badge (owner/admin/moderator/observer), online status indicator, connection status |
+| Member tap | Navigates to `GroupMemberInfoView` |
+| Member swipe actions | Block/unblock member, block/unblock for all (moderators) |
+
+Member list is sorted by role (owners first) and filtered to exclude `memLeft` and `memRemoved` statuses.
+
+### Danger Zone Section
+
+| Action | Description |
+|---|---|
+| Clear chat | Deletes all messages locally (with confirmation alert) |
+| Leave group | Leave the group (with confirmation alert) |
+| Delete group | Delete entire group -- only for owners (with confirmation alert) |
+
+### Developer Section
+
+Shown when `developerTools` is enabled:
+
+| Element | Description |
+|---|---|
+| Local name | Internal chat local display name |
+| Database ID | API entity ID |
+
+## Loading / Error States
+
+| State | Behavior |
+|---|---|
+| Loading members | Member list populated from `chatModel.groupMembers` |
+| Progress indicator | `ProgressView` overlay when `progressIndicator` is true (during TTL changes) |
+| Large group receipts | Receipts option disabled with "Disabled for large groups" label and info alert |
+| Incognito invite blocked | Alert: "Can't invite contacts when incognito" |
+| Errors | Alert with localized title and error description |
+
+## Alerts
+
+| Alert | Trigger |
+|---|---|
+| `deleteGroupAlert` | Tap delete group |
+| `clearChatAlert` | Tap clear chat |
+| `leaveGroupAlert` | Tap leave group |
+| `cantInviteIncognitoAlert` | Tap invite members while incognito |
+| `largeGroupReceiptsDisabled` | Tap receipts info on large group |
+| `blockMemberAlert` / `unblockMemberAlert` | Block/unblock member actions |
+| `blockForAllAlert` / `unblockForAllAlert` | Moderator block/unblock for all members |
+
+## Related Specs
+
+- `spec/api.md` -- Group API commands (create, update, add/remove members, roles, links)
+- [Chat](chat.md) -- Parent chat view
+- [Contact Info](contact-info.md) -- Similar pattern for direct contact info
+
+## Source Files
+
+- `Shared/Views/Chat/Group/GroupChatInfoView.swift` -- Main group info view with all sections
+- `Shared/Views/Chat/Group/GroupProfileView.swift` -- Edit group name, image, description
+- `Shared/Views/Chat/Group/AddGroupMembersView.swift` -- Member invitation view
+- `Shared/Views/Chat/Group/GroupLinkView.swift` -- Group link creation and management
+- `Shared/Views/Chat/Group/GroupPreferencesView.swift` -- Group feature preferences
+- `Shared/Views/Chat/Group/GroupWelcomeView.swift` -- Welcome message editor
+- `Shared/Views/Chat/Group/MemberAdmissionView.swift` -- Member admission policy settings
+- `Shared/Views/Chat/Group/GroupMemberInfoView.swift` -- Individual member info and actions
+- `Shared/Views/Chat/Group/GroupMentions.swift` -- @mention support in groups
diff --git a/apps/ios/product/views/new-chat.md b/apps/ios/product/views/new-chat.md
new file mode 100644
index 0000000000..e53659e622
--- /dev/null
+++ b/apps/ios/product/views/new-chat.md
@@ -0,0 +1,94 @@
+# New Chat / Connection
+
+> **Related spec:** [spec/client/navigation.md](../../spec/client/navigation.md)
+
+## Purpose
+
+Create new contacts, groups, or connect with others via one-time invitation links or by scanning/pasting SimpleX links. This is the primary onramp for establishing new E2E encrypted connections.
+
+## Route / Navigation
+
+- **Entry point**: Tap the new chat button (pencil icon) in `ChatListView` toolbar
+- **Presented by**: `NewChatSheet` modal from `ChatListView`
+- **Internal navigation**: `NewChatMenuButton` provides a dropdown with options:
+ - "New chat" -- opens `NewChatView`
+ - "Create group" -- opens `AddGroupView`
+- **Tabs within NewChatView**: Segmented picker toggles between `.invite` (1-time link) and `.connect` (connect via link)
+- **Swipe gesture**: Left/right swipe switches between invite and connect tabs
+- **Dismiss behavior**: On dismiss, `showKeepInvitationAlert()` asks whether to keep an unused invitation link or delete it
+
+## Page Sections
+
+### Segmented Picker
+
+| Tab | Icon | Description |
+|---|---|---|
+| 1-time link | `link` | Generate and share a one-time invitation link |
+| Connect via link | `qrcode` | Scan QR code or paste a received link |
+
+### Invite Tab (1-time Link)
+
+Displayed when `selection == .invite`:
+
+| Element | Description |
+|---|---|
+| QR code display | Generated QR code for the invitation link (`SimpleXLinkQRCode`) |
+| Short/full link toggle | Switch between short and full link display |
+| Share button | System share sheet for the invitation link |
+| Copy button | Copy link to clipboard |
+| Incognito toggle | Option to connect with a random profile |
+| Loading state | `creatingLinkProgressView` spinner while `creatingConnReq` is true |
+| Retry button | Shown if link creation fails |
+
+Link creation calls `apiAddContact` which returns a `CreatedConnLink` with both `connFullLink` and optional `connShortLink`.
+
+### Connect Tab (Connect via Link)
+
+Displayed when `selection == .connect`:
+
+| Element | Description |
+|---|---|
+| QR code scanner | Camera-based `CodeScanner` view for scanning SimpleX QR codes |
+| Paste link field | Text input for pasting a SimpleX link manually |
+| Connect button | Initiates connection via the pasted/scanned link |
+
+Handled by `ConnectView` sub-view with `showQRCodeScanner` state.
+
+### Info Sheet
+
+Toolbar trailing button opens `AddContactLearnMore` info sheet explaining how SimpleX connections work.
+
+### Add Group
+
+Accessed via `NewChatMenuButton` dropdown:
+
+| Element | Description |
+|---|---|
+| Group name | Required text field |
+| Group image | Optional profile image picker |
+| Incognito option | Create group with random profile |
+| Create button | Creates group via API and navigates to group chat |
+
+## Loading / Error States
+
+| State | Behavior |
+|---|---|
+| Creating invitation | `ProgressView` spinner shown; buttons disabled |
+| Link creation failure | Retry button displayed |
+| Invalid link pasted | Alert shown via `NewChatViewAlert.newChatSomeAlert` |
+| Connection in progress | Chat list shows pending connection entry |
+| Unused invitation on dismiss | Alert: "Keep unused invitation?" with Keep/Delete options |
+
+## Related Specs
+
+- `spec/api.md` -- API commands: `APIAddContact`, `APIConnect`, `APICreateUserAddress`
+- [Chat List](chat-list.md) -- Parent view that presents this sheet
+- [Chat](chat.md) -- Navigated to after successful connection
+
+## Source Files
+
+- `Shared/Views/NewChat/NewChatView.swift` -- Main view with invite/connect tabs, link generation
+- `Shared/Views/NewChat/NewChatMenuButton.swift` -- Dropdown menu (new chat, create group)
+- `Shared/Views/NewChat/QRCode.swift` -- QR code generation and display
+- `Shared/Views/NewChat/AddGroupView.swift` -- Group creation form
+- `Shared/Views/NewChat/AddContactLearnMore.swift` -- Info sheet explaining connection process
diff --git a/apps/ios/product/views/onboarding.md b/apps/ios/product/views/onboarding.md
new file mode 100644
index 0000000000..a283c25a19
--- /dev/null
+++ b/apps/ios/product/views/onboarding.md
@@ -0,0 +1,147 @@
+# Onboarding
+
+> **Related spec:** [spec/client/navigation.md](../../spec/client/navigation.md) | [spec/architecture.md](../../spec/architecture.md)
+
+## Purpose
+
+First-time setup flow for new users. Guides through app introduction, profile creation, server operator conditions acceptance, and notification configuration. Also provides an entry point for device migration.
+
+## Route / Navigation
+
+- **Entry point**: App launch when `onboardingStageDefault` is not `.onboardingComplete`
+- **Presented by**: `OnboardingView` renders the appropriate step based on `OnboardingStage` enum
+- **Flow direction**: Linear progression; back navigation hidden on later steps (`.navigationBarBackButtonHidden(true)`)
+- **Completion**: Sets `onboardingStageDefault` to `.onboardingComplete` and updates `chatModel.onboardingStage`
+
+## Onboarding Steps
+
+### Step 1: Welcome / SimpleX Info (`SimpleXInfo`)
+
+**Stage**: `step1_SimpleXInfo`
+
+| Element | Description |
+|---|---|
+| Logo | SimpleX Chat logo (light/dark variant based on color scheme) |
+| "The future of messaging" | Info button opening `HowItWorks` sheet |
+| Privacy redefined | "No user identifiers." with privacy icon |
+| Immune to spam | "You decide who can connect." with shield icon |
+| Decentralized | "Anybody can host servers." with decentralized icon |
+| **Create your profile** button | Primary action; navigates to `CreateFirstProfile` |
+| **Migrate from another device** button | Secondary action; opens `MigrateToDevice` sheet |
+
+The "How it works" sheet (`HowItWorks`) explains SimpleX's privacy model with an option to proceed to profile creation.
+
+### Step 2: Create Profile (`CreateFirstProfile`)
+
+**Stage**: `step2_CreateProfile` (deprecated -- now part of step 1 flow)
+
+| Element | Description |
+|---|---|
+| Display name field | Required; auto-focused after 1 second delay |
+| Validation | `mkValidName` check; alerts for invalid/duplicate names |
+| Create button | Calls profile creation API; advances to next step |
+
+Profile is stored locally and only shared with contacts. Footer explains this privacy property.
+
+### Step 3: Server Operator Conditions (`OnboardingConditionsView`)
+
+**Stage**: `step3_ChooseServerOperators` (changed to simplified conditions view)
+
+| Element | Description |
+|---|---|
+| "Conditions of use" title | Large title header |
+| Privacy explanation | "Private chats, groups and your contacts are not accessible to server operators." |
+| Operator selection | Toggle operators (with `selectedOperatorIds`) |
+| Show conditions | Sheet to view full conditions (`ConditionsWebView`) |
+| Configure operators | Sheet to customize operator settings |
+| **Accept** button | Accepts conditions and advances to notifications step |
+
+Previous deprecated step `step3_CreateSimpleXAddress` (`CreateSimpleXAddress`) is no longer in the active flow.
+
+### Step 4: Set Notification Mode (`SetNotificationsMode`)
+
+**Stage**: `step4_SetNotificationsMode`
+
+| Element | Description |
+|---|---|
+| "Push notifications" title | Large title header |
+| Info text | Explanation of notification modes |
+| Mode selector | `NtfModeSelector` for each `NotificationsMode.values` |
+| **Enable notifications** / **Use chat** button | Sets notification mode and completes onboarding |
+| Info sheet | `NotificationsInfoView` accessible for detailed explanation |
+
+Notification modes:
+
+| Mode | Description |
+|---|---|
+| Instant | Background connection maintained; real-time notifications |
+| Periodic | Checks every 10 minutes; battery-friendly |
+| Off | No push notifications; messages received only when app is open |
+
+On completion, `onboardingStageDefault.set(.onboardingComplete)` is called.
+
+### Completion
+
+**Stage**: `onboardingComplete`
+
+`OnboardingView` renders `EmptyView()` and the app proceeds to `ChatListView`.
+
+## Optional Paths
+
+### Migrate from Another Device
+
+- Triggered from Step 1 via "Migrate from another device" button
+- Sets `chatModel.migrationState = .pasteOrScanLink`
+- Opens `MigrateToDevice` in a sheet within `NavigationView`
+- User pastes or scans a migration link from the source device
+- Imports database and settings from the linked device
+
+### What's New (`WhatsNewView`)
+
+- Not part of the linear onboarding flow
+- Shown when `DEFAULT_WHATS_NEW_VERSION` differs from current version
+- Accessible later from Settings > Help > What's new
+- Displays changelog with feature descriptions
+
+## Onboarding Stage Enum
+
+```
+enum OnboardingStage: String {
+ case step1_SimpleXInfo
+ case step2_CreateProfile // deprecated
+ case step3_CreateSimpleXAddress // deprecated
+ case step3_ChooseServerOperators // conditions acceptance
+ case step4_SetNotificationsMode
+ case onboardingComplete
+}
+```
+
+Persisted via `DEFAULT_ONBOARDING_STAGE` in `UserDefaults`.
+
+## Loading / Error States
+
+| State | Behavior |
+|---|---|
+| No device token | Alert "No device token!" if trying to set notification mode without token |
+| Profile creation error | Alert with error description |
+| Migration failure | Error handling within `MigrateToDevice` flow |
+| Conditions loading | Async fetch of operator conditions |
+
+## Related Specs
+
+- `spec/architecture.md` -- App architecture and initialization flow
+- [Chat List](chat-list.md) -- Destination after onboarding completes
+- [User Profiles](user-profiles.md) -- Profile created during onboarding; additional profiles later
+- [Settings](settings.md) -- Notification and server settings revisitable after onboarding
+
+## Source Files
+
+- `Shared/Views/Onboarding/OnboardingView.swift` -- Step router and `OnboardingStage` enum definition
+- `Shared/Views/Onboarding/SimpleXInfo.swift` -- Step 1: Welcome screen with privacy highlights and migration entry
+- `Shared/Views/Onboarding/CreateProfile.swift` -- Profile creation form (shared between onboarding and user profiles)
+- `Shared/Views/Onboarding/CreateSimpleXAddress.swift` -- Deprecated step 3: SimpleX address creation
+- `Shared/Views/Onboarding/ChooseServerOperators.swift` -- Step 3: Server operator conditions and selection
+- `Shared/Views/Onboarding/SetNotificationsMode.swift` -- Step 4: Push notification mode selection
+- `Shared/Views/Onboarding/HowItWorks.swift` -- "How it works" info sheet from step 1
+- `Shared/Views/Onboarding/WhatsNewView.swift` -- Changelog / what's new display
+- `Shared/Views/Onboarding/AddressCreationCard.swift` -- Address creation prompt card
diff --git a/apps/ios/product/views/settings.md b/apps/ios/product/views/settings.md
new file mode 100644
index 0000000000..58507ce52b
--- /dev/null
+++ b/apps/ios/product/views/settings.md
@@ -0,0 +1,172 @@
+# Settings
+
+> **Related spec:** [spec/client/navigation.md](../../spec/client/navigation.md) | [spec/services/theme.md](../../spec/services/theme.md) | [spec/services/notifications.md](../../spec/services/notifications.md)
+
+## Purpose
+
+Configure all aspects of app behavior including notifications, network/servers, privacy, appearance, database management, call settings, and developer tools. Accessed from the UserPicker sheet on the chat list.
+
+## Route / Navigation
+
+- **Entry point**: Tap user avatar in `ChatListView` toolbar -> `UserPicker` -> Settings option
+- **Presented by**: `UserPickerSheetView(sheet: .settings)` wrapping `SettingsView` in a `NavigationView`
+- **Navigation title**: "Your settings"
+- **Sub-navigation**: Each settings row is a `NavigationLink` to a dedicated settings view
+
+## Page Sections
+
+### Settings Section
+
+| Row | Icon | Destination | Description |
+|---|---|---|---|
+| Notifications | `bolt` (color varies by token status) | `NotificationsView` | Push notification mode and preview settings |
+| Network & servers | `externaldrive.connected.to.line.below` | `NetworkAndServers` | SMP/XFTP servers, proxy, .onion hosts, advanced network |
+| Audio & video calls | `video` | `CallSettings` | WebRTC relay policy, ICE servers, CallKit options |
+| Privacy & security | `lock` | `PrivacySettings` | SimpleX Lock, screen protection, delivery receipts, auto-accept |
+| Appearance | `sun.max` | `AppearanceSettings` | Theme, language, wallpapers, chat bubbles, toolbar opacity |
+
+All rows disabled when `chatModel.chatRunning != true`. Appearance row only shown when `UIApplication.shared.supportsAlternateIcons`.
+
+#### Notifications (`NotificationsView`)
+
+| Setting | Options |
+|---|---|
+| Notification mode | Instant (background connection) / Periodic (every 10 min) / Off |
+| Notification preview | Hidden / Contact name only / Message preview |
+| Token status indicator | Icon color reflects: new, registered, confirmed (yellow), active (green), expired, invalid |
+
+#### Network & Servers (`NetworkAndServers`)
+
+| Setting | Description |
+|---|---|
+| SMP servers | Messaging relay servers; per-operator configuration |
+| XFTP servers | File transfer servers; per-operator configuration |
+| Server operators | `OperatorView` for each configured operator |
+| Advanced network | `AdvancedNetworkSettings` -- timeouts, TCP keep-alive, reconnect intervals |
+| Proxy configuration | SOCKS proxy, .onion host settings |
+| Show sent via proxy | Toggle to show proxy indicator on sent messages |
+| Show subscription % | Toggle to show server subscription percentage |
+
+Sub-files: `NetworkAndServers.swift`, `ProtocolServersView.swift`, `ProtocolServerView.swift`, `NewServerView.swift`, `ScanProtocolServer.swift`, `AdvancedNetworkSettings.swift`, `OperatorView.swift`, `ConditionsWebView.swift`
+
+#### Privacy & Security (`PrivacySettings`)
+
+| Setting | Description |
+|---|---|
+| SimpleX Lock | Enable biometric (Face ID / Touch ID) or passcode lock |
+| Lock mode | System biometric or custom passcode |
+| Lock timeout | Delay before lock activates (0s to 30min) |
+| Self-destruct | Optional self-destruct passcode that wipes all data |
+| Screen protection | Hide app content in app switcher |
+| Encrypt local files | Encrypt media and files stored on device |
+| Auto-accept images | Automatically download received images |
+| Link previews | Generate link previews for sent URLs |
+| SimpleX link mode | Description / Full link / Via browser |
+| Chat previews | Show message previews in chat list |
+| Save last draft | Remember unsent message drafts |
+| Delivery receipts | Enable/disable read receipts globally |
+| Media blur radius | Blur level for received media before tapping |
+
+#### Appearance (`AppearanceSettings`)
+
+| Setting | Description |
+|---|---|
+| App icon | Alternative app icon selection |
+| Language | Interface language |
+| Theme | System / Light / Dark |
+| Dark theme variant | Dark / SimpleX / Black |
+| Active theme colors | Accent color, chat bubble colors, text colors |
+| Wallpapers | Chat background wallpaper selection and customization |
+| Profile image corner radius | Adjust avatar roundness |
+| Chat bubble roundness | Adjust message bubble corner radius |
+| Chat bubble tail | Toggle message bubble tail/pointer |
+| Toolbar opacity | `ToolbarMaterial` transparency setting |
+| One-hand UI | Bottom toolbar layout for reachability |
+
+#### Audio & Video Calls (`CallSettings`)
+
+| Setting | Description |
+|---|---|
+| WebRTC relay policy | Always relay / Allow direct |
+| ICE servers | Custom STUN/TURN server configuration |
+| CallKit integration | Enable/disable native iOS call UI |
+| Calls in recents | Show/hide calls in Phone app history |
+| Lock screen calls | Show/accept on lock screen options |
+
+### Chat Database Section
+
+| Row | Icon | Destination | Description |
+|---|---|---|---|
+| Database passphrase & export | `internaldrive` (orange if unencrypted) | `DatabaseView` | Passphrase management, export/import database, file storage stats |
+| Migrate to another device | `tray.and.arrow.up` | `MigrateFromDevice` | Export database and generate migration link |
+
+Database row shows exclamation octagon icon in red when `chatRunning == false`.
+
+### Help Section
+
+| Row | Icon | Destination | Description |
+|---|---|---|---|
+| How to use it | `questionmark` | `ChatHelp` | Usage guide with user's display name |
+| What's new | `plus` | `WhatsNewView` | Changelog and new features |
+| About SimpleX Chat | `info` | `SimpleXInfo` | About page with privacy explanation |
+| Send questions and ideas | `number` | Opens SimpleX team chat link | Direct contact with developers |
+| Send us email | `envelope` | `mailto:chat@simplex.chat` | Email link |
+
+### Support SimpleX Chat Section
+
+| Row | Icon | Action |
+|---|---|---|
+| Contribute | `keyboard` | Opens GitHub contribution guide |
+| Rate the app | `star` | `SKStoreReviewController.requestReview` |
+| Star on GitHub | GitHub icon | Opens GitHub repository |
+
+### Develop Section
+
+| Row | Icon | Destination | Description |
+|---|---|---|---|
+| Developer tools | `chevron.left.forwardslash.chevron.right` | `DeveloperView` | Chat console/terminal, log level, confirm DB upgrades |
+| App version | (none) | `VersionView` | Shows "v{version} ({build})" |
+
+## Loading / Error States
+
+| State | Behavior |
+|---|---|
+| Chat not running | Most navigation links disabled; database row shows warning |
+| Database not encrypted | Database icon shown in orange |
+| Migration in progress | `showProgress` overlays `ProgressView` on entire settings view |
+| Terminal cleanup | On disappear: `chatModel.showingTerminal = false`, terminal items cleared |
+
+## App Defaults
+
+Key `UserDefaults` / `AppStorage` keys managed by settings:
+- `DEFAULT_PERFORM_LA`, `DEFAULT_LA_MODE`, `DEFAULT_LA_LOCK_DELAY`, `DEFAULT_LA_SELF_DESTRUCT`
+- `DEFAULT_PRIVACY_ACCEPT_IMAGES`, `DEFAULT_PRIVACY_LINK_PREVIEWS`, `DEFAULT_PRIVACY_PROTECT_SCREEN`
+- `DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS`, `DEFAULT_PRIVACY_SAVE_LAST_DRAFT`
+- `DEFAULT_PRIVACY_DELIVERY_RECEIPTS_SET`, `DEFAULT_PRIVACY_MEDIA_BLUR_RADIUS`
+- `DEFAULT_WEBRTC_POLICY_RELAY`, `DEFAULT_WEBRTC_ICE_SERVERS`, `DEFAULT_CALL_KIT_CALLS_IN_RECENTS`
+- `DEFAULT_CURRENT_THEME`, `DEFAULT_SYSTEM_DARK_THEME`, `DEFAULT_THEME_OVERRIDES`
+- `DEFAULT_PROFILE_IMAGE_CORNER_RADIUS`, `DEFAULT_CHAT_ITEM_ROUNDNESS`, `DEFAULT_CHAT_ITEM_TAIL`
+- `DEFAULT_TOOLBAR_MATERIAL`, `DEFAULT_ONE_HAND_UI_CARD_SHOWN`
+- `DEFAULT_DEVELOPER_TOOLS`, `DEFAULT_SHOW_SENT_VIA_RPOXY`, `DEFAULT_SHOW_SUBSCRIPTION_PERCENTAGE`
+
+## Related Specs
+
+- `spec/architecture.md` -- App architecture overview
+- `spec/services/theme.md` -- Theme system specification
+- [Chat List](chat-list.md) -- Parent view via UserPicker
+- [User Profiles](user-profiles.md) -- Profile management (separate UserPicker option)
+
+## Source Files
+
+- `Shared/Views/UserSettings/SettingsView.swift` -- Main settings view, section layout, app defaults definitions
+- `Shared/Views/UserSettings/NotificationsView.swift` -- Notification mode and preview settings
+- `Shared/Views/UserSettings/AppearanceSettings.swift` -- Theme, wallpaper, UI customization
+- `Shared/Views/UserSettings/PrivacySettings.swift` -- Privacy and security settings
+- `Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift` -- Server and network configuration
+- `Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift` -- TCP/timeout settings
+- `Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift` -- SMP/XFTP server list
+- `Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift` -- Individual server edit
+- `Shared/Views/UserSettings/NetworkAndServers/NewServerView.swift` -- Add new server
+- `Shared/Views/UserSettings/NetworkAndServers/ScanProtocolServer.swift` -- Scan server QR code
+- `Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift` -- Server operator configuration
+- `Shared/Views/UserSettings/NetworkAndServers/ConditionsWebView.swift` -- Operator conditions display
diff --git a/apps/ios/product/views/user-profiles.md b/apps/ios/product/views/user-profiles.md
new file mode 100644
index 0000000000..5a38db1816
--- /dev/null
+++ b/apps/ios/product/views/user-profiles.md
@@ -0,0 +1,137 @@
+# User Profiles
+
+> **Related spec:** [spec/client/navigation.md](../../spec/client/navigation.md) | [spec/state.md](../../spec/state.md)
+
+## Purpose
+
+Manage multiple chat profiles within a single app instance. Users can create, switch between, hide, mute, and delete profiles. Hidden profiles are protected by password and support a self-destruct password option.
+
+## Route / Navigation
+
+- **Entry point**: Tap user avatar in `ChatListView` toolbar -> `UserPicker` -> "Your chat profiles"
+- **Presented by**: `UserPickerSheetView(sheet: .chatProfiles)` wrapping `UserProfilesView` in a `NavigationView`
+- **Navigation title**: "Your chat profiles"
+- **Sub-navigation**:
+ - Create profile -> `CreateProfile`
+ - Edit profile -> profile detail view (via `selectedUser`)
+ - User address -> `UserAddressView` (via UserPicker `.address` sheet)
+
+## Page Sections
+
+### Search / Password Field
+
+Combined text field at the top (`searchTextOrPassword`):
+- In normal mode: Filters visible profiles by name
+- For hidden profiles: Acts as password entry to reveal hidden profiles
+- Trimmed search text compared against profile names and hidden profile passwords
+
+### Profile List
+
+Each row rendered by `userView()`:
+
+| Element | Description |
+|---|---|
+| Active indicator | Checkmark or highlighted state for the current active profile |
+| Profile image | Avatar circle with profile image or colored initials |
+| Display name | Profile's display name |
+| Unread count | Badge showing unread message count across all chats for this profile |
+| Muted indicator | Bell-slash icon if profile notifications are muted |
+| Hidden indicator | Lock icon for hidden profiles (only shown when revealed via password) |
+
+### Profile Actions
+
+Available via tap on a profile row:
+
+| Action | Condition | Description |
+|---|---|---|
+| Switch active | Different from current | Activates the selected profile; all chats switch context |
+| Mute / Unmute | Any profile | Toggle notification muting for the profile; shows alert on first mute (`showMuteProfileAlert`) |
+| Hide / Unhide | Non-active profile | Hide with password or reveal a hidden profile |
+| Delete | Non-active profile | Delete with confirmation; option to delete data from servers |
+
+### Add Profile Button
+
+| Element | Description |
+|---|---|
+| "Add profile" label | `Label("Add profile", systemImage: "plus")` |
+| Navigation | `NavigationLink` to `CreateProfile` view |
+| Auth required | Requires local authentication before creating |
+
+Only shown when `trimmedSearchTextOrPassword` is empty (not searching/entering password).
+
+### Hidden Profile Banner
+
+Shown when `profileHidden` is true (a profile was just hidden):
+
+| Element | Description |
+|---|---|
+| Lock icon | `lock.open` system image |
+| Message | "Enter password above to show!" |
+| Tap action | Dismisses the banner with animation |
+
+### Create Profile (`CreateProfile`)
+
+| Field | Description |
+|---|---|
+| Display name | Required text field with validation (`mkValidName`) |
+| Bio | Optional bio text (max 160 bytes) |
+| Create button | Disabled until valid name entered and bio within limit |
+
+Validation alerts: `duplicateUserError`, `invalidDisplayNameError`, `createUserError`, `invalidNameError`.
+
+## Profile Visibility
+
+| Visibility | Description |
+|---|---|
+| Public | Normal profile, always visible in the list |
+| Hidden | Protected by password; not shown unless password entered in search field |
+| Muted | Notifications suppressed; visual indicator in profile list |
+
+### Hidden Profile Password Management
+
+- Set password when hiding a profile
+- Password verified when entering in the search/password field
+- `UserProfileAction.unhideUser` requires password entry
+- Self-destruct password: Optional secondary password (`DEFAULT_LA_SELF_DESTRUCT`) that wipes all app data when entered
+
+### Delete Profile
+
+Two-stage confirmation:
+
+1. `confirmDeleteUser()` shows initial confirmation
+2. `UserProfilesAlert.deleteUser(user:, delSMPQueues:)` with option to delete queues from servers
+3. Requires local authentication (`withAuth`) before proceeding
+
+## Loading / Error States
+
+| State | Behavior |
+|---|---|
+| Authentication required | `authorized` state; prompts biometric/passcode before profile operations |
+| Profile switch | Async operation; profile switch errors shown via `activateUserError` alert |
+| Delete in progress | Profile removed from list; server queue deletion is async |
+| Errors | Alert with localized error title and description |
+
+## Alerts
+
+| Alert | Trigger |
+|---|---|
+| `deleteUser` | Confirm profile deletion |
+| `hiddenProfilesNotice` | First-time hidden profiles explanation (`showHiddenProfilesNotice`) |
+| `muteProfileAlert` | First-time mute explanation (`showMuteProfileAlert`) |
+| `activateUserError` | Profile switch failure |
+| `error` | General error display |
+
+## Related Specs
+
+- `spec/api.md` -- User management API commands (create user, delete user, activate user, hide user)
+- `spec/state.md` -- Application state: `chatModel.users`, `chatModel.currentUser`
+- [Chat List](chat-list.md) -- Reflects active profile's chats
+- [Settings](settings.md) -- Accessed from same UserPicker menu
+- [Onboarding](onboarding.md) -- Initial profile creation during first launch
+
+## Source Files
+
+- `Shared/Views/UserSettings/UserProfilesView.swift` -- Main profiles list, search/password, profile actions, delete confirmation
+- `Shared/Views/Onboarding/CreateProfile.swift` -- Profile creation form (shared with onboarding and profiles view)
+- `Shared/Views/UserSettings/UserAddressView.swift` -- User's SimpleX address management (create, share, delete)
+- `Shared/Views/ChatList/UserPicker.swift` -- Profile switcher sheet that navigates to this view
diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings
index 0826bca4a3..87a47ec2ab 100644
--- a/apps/ios/ru.lproj/Localizable.strings
+++ b/apps/ios/ru.lproj/Localizable.strings
@@ -164,7 +164,7 @@
"%d file(s) were not downloaded." = "%d файлов не было загружено.";
/* time interval */
-"%d hours" = "%d час.";
+"%d hours" = "%d ч.";
/* alert title */
"%d messages not forwarded" = "%d сообщений не переслано";
@@ -729,7 +729,7 @@ swipe action */
"attempts" = "попытки";
/* No comment provided by engineer. */
-"Audio & video calls" = "Аудио- и видеозвонки";
+"Audio & video calls" = "Аудио и видеозвонки";
/* No comment provided by engineer. */
"Audio and video calls" = "Аудио и видео звонки";
@@ -1733,7 +1733,8 @@ swipe action */
/* No comment provided by engineer. */
"Delete message?" = "Удалить сообщение?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "Удалить сообщения";
/* No comment provided by engineer. */
@@ -2238,6 +2239,9 @@ chat item action */
/* alert message */
"Error connecting to forwarding server %@. Please try later." = "Ошибка подключения к пересылающему серверу %@. Попробуйте позже.";
+/* subscription status explanation */
+"Error connecting to the server used to receive messages from this connection: %@" = "Ошибка подключения к серверу, используемому для получения сообщений от этого соединения: %@";
+
/* No comment provided by engineer. */
"Error creating address" = "Ошибка при создании адреса";
@@ -3305,10 +3309,10 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "Роль члена будет изменена на \"%@\". Будет отправлено новое приглашение.";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from chat - this cannot be undone!" = "Член будет удален из разговора - это действие нельзя отменить!";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "Член группы будет удален - это действие нельзя отменить!";
/* alert message */
@@ -3710,6 +3714,9 @@ snd error text */
/* servers error */
"No servers to send files." = "Нет серверов для отправки файлов.";
+/* No comment provided by engineer. */
+"no subscription" = "нет подписки";
+
/* copied message info in history */
"no text" = "нет текста";
@@ -4374,7 +4381,7 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "Relay сервер защищает Ваш IP адрес, но может отслеживать продолжительность звонка.";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "Удалить";
/* No comment provided by engineer. */
@@ -4389,7 +4396,7 @@ swipe action */
/* No comment provided by engineer. */
"Remove member" = "Удалить члена группы";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "Удалить члена группы?";
/* No comment provided by engineer. */
@@ -5545,7 +5552,7 @@ report reason */
"To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page." = "Чтобы показать Ваш скрытый профиль, введите его пароль в поле поиска на странице **Ваши профили чата**.";
/* No comment provided by engineer. */
-"To send" = "Для оправки";
+"To send" = "Для отправки";
/* alert message */
"To send commands you must be connected." = "Вы должны быть соединены, чтобы отправлять команды.";
@@ -5583,6 +5590,9 @@ report reason */
/* No comment provided by engineer. */
"Transport sessions" = "Транспортные сессии";
+/* subscription status explanation */
+"Trying to connect to the server used to receive messages from this connection." = "Попытка подключиться к серверу, используемому для получения сообщений от этого соединения.";
+
/* No comment provided by engineer. */
"Turkish interface" = "Турецкий интерфейс";
@@ -6075,9 +6085,15 @@ report reason */
/* new chat sheet title */
"You are already joining the group!\nRepeat join request?" = "Вы уже вступаете в группу!\nПовторить запрос на вступление?";
+/* subscription status explanation */
+"You are connected to the server used to receive messages from this connection." = "Вы подключены к серверу, используемому для приема сообщений от этого соединения.";
+
/* No comment provided by engineer. */
"You are invited to group" = "Вы приглашены в группу";
+/* subscription status explanation */
+"You are not connected to the server used to receive messages from this connection (no subscription)." = "Вы не подключены к серверу, используемому для получения сообщений по этому соединению (нет подписки).";
+
/* No comment provided by engineer. */
"You are not connected to these servers. Private routing is used to deliver messages to them." = "Вы не подключены к этим серверам. Для доставки сообщений на них используется конфиденциальная доставка.";
diff --git a/apps/ios/spec/README.md b/apps/ios/spec/README.md
new file mode 100644
index 0000000000..eca6103582
--- /dev/null
+++ b/apps/ios/spec/README.md
@@ -0,0 +1,74 @@
+# SimpleX Chat iOS -- Specification Overview
+
+> Technical specification suite for the SimpleX Chat iOS application. Each document provides bidirectional links to product documentation and source code.
+
+## Executive Summary
+
+The SimpleX Chat iOS app is a native SwiftUI frontend that communicates with a Haskell core library via C FFI. All chat logic, encryption, protocol handling, and database operations happen in the Haskell core (`chat_ctrl`). The iOS layer handles UI rendering, system integration (CallKit, Push Notifications, Background Tasks), local preferences, and theming. The app shares its database with a Notification Service Extension (NSE) for decrypting push payloads while the main app is inactive.
+
+## Dependency Graph
+
+```
+SimpleXApp (root entry point)
+├── ChatModel (ObservableObject state) <-> SimpleXAPI (FFI bridge) <-> Haskell Core (chat_ctrl)
+├── Views (SwiftUI)
+│ ├── ChatListView -> ChatView -> ComposeView
+│ ├── ChatItemView (renders individual messages)
+│ ├── Settings, UserProfiles, Onboarding
+│ └── ActiveCallView (WebRTC + CallKit)
+├── Models
+│ ├── ChatModel (global app state -- singleton)
+│ ├── ItemsModel (per-chat message list state -- singleton + secondary instances)
+│ ├── ChatTagsModel (tag filtering state)
+│ └── Chat (per-conversation observable state)
+├── Services
+│ ├── NtfManager (push notification coordination)
+│ ├── BGManager (background task scheduling)
+│ ├── CallController (CallKit + VoIP push)
+│ └── ThemeManager (theme resolution engine)
+└── Extensions
+ ├── SimpleX NSE (Notification Service Extension -- decrypts push payloads)
+ └── SimpleX SE (Share Extension)
+```
+
+## Specification Documents
+
+| Document | Description |
+|----------|-------------|
+| [Architecture](architecture.md) | System architecture, FFI bridge, app lifecycle, extension model |
+| [Chat API Reference](api.md) | Complete ChatCommand, ChatResponse, ChatEvent, ChatError type reference |
+| [State Management](state.md) | ChatModel, ItemsModel, Chat, ChatInfo, preference storage |
+| [Database & Storage](database.md) | SQLite databases, encryption, file storage, export/import |
+| [Chat View](client/chat-view.md) | Message rendering, chat item types, context menu actions |
+| [Chat List](client/chat-list.md) | Conversation list, filtering, search, swipe actions |
+| [Message Composition](client/compose.md) | Compose bar, attachments, reply/edit/forward modes, voice recording |
+| [Navigation](client/navigation.md) | Navigation stack, deep linking, sheet presentation, call overlay |
+| [Push Notifications](services/notifications.md) | NtfManager, NSE, notification modes, token lifecycle |
+| [WebRTC Calling](services/calls.md) | CallController, WebRTCClient, CallKit, signaling via SMP |
+| [File Transfer](services/files.md) | Inline/XFTP transfer, auto-receive, CryptoFile, file constants |
+| [Theme Engine](services/theme.md) | ThemeManager, default themes, customization layers, wallpapers |
+| [Impact Graph](impact.md) | Source file → product concept mapping, risk levels |
+
+## Related Product Documentation
+
+- [Product Overview](../product/README.md)
+- [Concept Index](../product/concepts.md)
+- [Business Rules](../product/rules.md)
+- [Known Gaps](../product/gaps.md)
+- [Glossary](../product/glossary.md)
+- [Chat List View](../product/views/chat-list.md)
+- [Chat View](../product/views/chat.md)
+
+## Source Code Entry Points
+
+| File | Role |
+|------|------|
+| `Shared/SimpleXApp.swift` | App entry point, Haskell init, lifecycle management |
+| `Shared/AppDelegate.swift` | UIApplicationDelegate for push token registration |
+| `Shared/ContentView.swift` | Root view -- authentication gate, call overlay, navigation |
+| `Shared/Model/ChatModel.swift` | Primary observable state (ChatModel, ItemsModel, Chat) |
+| `Shared/Model/SimpleXAPI.swift` | FFI bridge -- chatSendCmd, chatApiSendCmd, sendSimpleXCmd |
+| `Shared/Model/AppAPITypes.swift` | ChatCommand, ChatResponse, ChatEvent enums (iOS app layer) |
+| `SimpleXChat/APITypes.swift` | APIResult, ChatError, ChatCmdProtocol (shared framework) |
+| `SimpleXChat/ChatTypes.swift` | User, ChatInfo, Contact, GroupInfo, ChatItem data types |
+| `SimpleXChat/SimpleX.h` | C header for Haskell FFI functions |
diff --git a/apps/ios/spec/api.md b/apps/ios/spec/api.md
new file mode 100644
index 0000000000..fe40a4c4ec
--- /dev/null
+++ b/apps/ios/spec/api.md
@@ -0,0 +1,600 @@
+# SimpleX Chat iOS -- Chat API Reference
+
+> Complete specification of the ChatCommand, ChatResponse, ChatEvent, and ChatError types that form the API between the Swift UI layer and the Haskell core.
+>
+> Related specs: [Architecture](architecture.md) | [State Management](state.md) | [README](README.md)
+> Related product: [Concept Index](../product/concepts.md)
+
+**Source:** [`AppAPITypes.swift`](../Shared/Model/AppAPITypes.swift) | [`SimpleXAPI.swift`](../Shared/Model/SimpleXAPI.swift) | [`APITypes.swift`](../SimpleXChat/APITypes.swift) | [`API.swift`](../SimpleXChat/API.swift)
+
+---
+
+## Table of Contents
+
+1. [Overview](#1-overview)
+2. [Command Categories (ChatCommand)](#2-command-categories)
+3. [Response Types (ChatResponse)](#3-response-types)
+4. [Event Types (ChatEvent)](#4-event-types)
+5. [Error Types (ChatError)](#5-error-types)
+6. [FFI Bridge Functions](#6-ffi-bridge-functions)
+7. [Result Type (APIResult)](#7-result-type)
+
+---
+
+## 1. Overview
+
+The iOS app communicates with the Haskell core exclusively through a command/response protocol:
+
+1. Swift constructs a `ChatCommand` enum value
+2. The command's `cmdString` property serializes it to a text command
+3. The FFI bridge sends the string to Haskell via `chat_send_cmd_retry`
+4. Haskell returns a JSON response, decoded as `APIResult`
+5. Async events arrive separately via `chat_recv_msg_wait`, decoded as `ChatEvent`
+
+**Source files**:
+- [`Shared/Model/AppAPITypes.swift`](../Shared/Model/AppAPITypes.swift) -- `ChatCommand` ([L14](../Shared/Model/AppAPITypes.swift#L15)), `ChatResponse0` ([L647](../Shared/Model/AppAPITypes.swift#L649)), `ChatResponse1` ([L768](../Shared/Model/AppAPITypes.swift#L771)), `ChatResponse2` ([L907](../Shared/Model/AppAPITypes.swift#L911)), `ChatEvent` ([L1050](../Shared/Model/AppAPITypes.swift#L1055)) enums
+- [`SimpleXChat/APITypes.swift`](../SimpleXChat/APITypes.swift) -- `APIResult` ([L26](../SimpleXChat/APITypes.swift#L27)), `ChatAPIResult` ([L63](../SimpleXChat/APITypes.swift#L65)), `ChatError` ([L695](../SimpleXChat/APITypes.swift#L699))
+- [`Shared/Model/SimpleXAPI.swift`](../Shared/Model/SimpleXAPI.swift) -- FFI bridge functions (`chatSendCmd` [L117](../Shared/Model/SimpleXAPI.swift#L121), `chatRecvMsg` [L230](../Shared/Model/SimpleXAPI.swift#L237))
+- [`SimpleXChat/API.swift`](../SimpleXChat/API.swift) -- Low-level FFI (`sendSimpleXCmd` [L114](../SimpleXChat/API.swift#L115), `recvSimpleXMsg` [L136](../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`)
+
+---
+
+## 2. Command Categories
+
+The `ChatCommand` enum ([`AppAPITypes.swift` L14](../Shared/Model/AppAPITypes.swift#L15)) contains all commands the iOS app can send to the Haskell core. Commands are organized below by functional area.
+
+### 2.1 User Management
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `showActiveUser` | -- | Get current active user | [L15](../Shared/Model/AppAPITypes.swift#L16) |
+| `createActiveUser` | `profile: Profile?, pastTimestamp: Bool` | Create new user profile | [L16](../Shared/Model/AppAPITypes.swift#L17) |
+| `listUsers` | -- | List all user profiles | [L17](../Shared/Model/AppAPITypes.swift#L18) |
+| `apiSetActiveUser` | `userId: Int64, viewPwd: String?` | Switch active user | [L18](../Shared/Model/AppAPITypes.swift#L19) |
+| `apiHideUser` | `userId: Int64, viewPwd: String` | Hide user behind password | [L23](../Shared/Model/AppAPITypes.swift#L24) |
+| `apiUnhideUser` | `userId: Int64, viewPwd: String` | Unhide hidden user | [L24](../Shared/Model/AppAPITypes.swift#L25) |
+| `apiMuteUser` | `userId: Int64` | Mute notifications for user | [L25](../Shared/Model/AppAPITypes.swift#L26) |
+| `apiUnmuteUser` | `userId: Int64` | Unmute notifications for user | [L26](../Shared/Model/AppAPITypes.swift#L27) |
+| `apiDeleteUser` | `userId: Int64, delSMPQueues: Bool, viewPwd: String?` | Delete user profile | [L27](../Shared/Model/AppAPITypes.swift#L28) |
+| `apiUpdateProfile` | `userId: Int64, profile: Profile` | Update user display name/image | [L138](../Shared/Model/AppAPITypes.swift#L139) |
+| `setAllContactReceipts` | `enable: Bool` | Set delivery receipts for all contacts | [L19](../Shared/Model/AppAPITypes.swift#L20) |
+| `apiSetUserContactReceipts` | `userId: Int64, userMsgReceiptSettings` | Per-user contact receipt settings | [L20](../Shared/Model/AppAPITypes.swift#L21) |
+| `apiSetUserGroupReceipts` | `userId: Int64, userMsgReceiptSettings` | Per-user group receipt settings | [L21](../Shared/Model/AppAPITypes.swift#L22) |
+| `apiSetUserAutoAcceptMemberContacts` | `userId: Int64, enable: Bool` | Auto-accept group member contacts | [L22](../Shared/Model/AppAPITypes.swift#L23) |
+
+### 2.2 Chat Lifecycle Control
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `startChat` | `mainApp: Bool, enableSndFiles: Bool` | Start chat engine | [L28](../Shared/Model/AppAPITypes.swift#L29) |
+| `checkChatRunning` | -- | Check if chat is running | [L29](../Shared/Model/AppAPITypes.swift#L30) |
+| `apiStopChat` | -- | Stop chat engine | [L30](../Shared/Model/AppAPITypes.swift#L31) |
+| `apiActivateChat` | `restoreChat: Bool` | Resume from background | [L31](../Shared/Model/AppAPITypes.swift#L32) |
+| `apiSuspendChat` | `timeoutMicroseconds: Int` | Suspend for background | [L32](../Shared/Model/AppAPITypes.swift#L33) |
+| `apiSetAppFilePaths` | `filesFolder, tempFolder, assetsFolder` | Set file storage paths | [L33](../Shared/Model/AppAPITypes.swift#L34) |
+| `apiSetEncryptLocalFiles` | `enable: Bool` | Toggle local file encryption | [L34](../Shared/Model/AppAPITypes.swift#L35) |
+
+### 2.3 Chat & Message Operations
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `apiGetChats` | `userId: Int64` | Get all chat previews for user | [L43](../Shared/Model/AppAPITypes.swift#L44) |
+| `apiGetChat` | `chatId, scope, contentTag, pagination, search` | Get messages for a chat | [L44](../Shared/Model/AppAPITypes.swift#L45) |
+| `apiGetChatContentTypes` | `chatId, scope` | Get content type counts for a chat | [L45](../Shared/Model/AppAPITypes.swift#L46) |
+| `apiGetChatItemInfo` | `type, id, scope, itemId` | Get detailed info for a message | [L46](../Shared/Model/AppAPITypes.swift#L47) |
+| `apiSendMessages` | `type, id, scope, live, ttl, composedMessages` | Send one or more messages | [L47](../Shared/Model/AppAPITypes.swift#L48) |
+| `apiCreateChatItems` | `noteFolderId, composedMessages` | Create items in notes folder | [L53](../Shared/Model/AppAPITypes.swift#L54) |
+| `apiUpdateChatItem` | `type, id, scope, itemId, updatedMessage, live` | Edit a sent message | [L55](../Shared/Model/AppAPITypes.swift#L56) |
+| `apiDeleteChatItem` | `type, id, scope, itemIds, mode` | Delete messages | [L56](../Shared/Model/AppAPITypes.swift#L57) |
+| `apiDeleteMemberChatItem` | `groupId, itemIds` | Moderate group messages | [L57](../Shared/Model/AppAPITypes.swift#L58) |
+| `apiChatItemReaction` | `type, id, scope, itemId, add, reaction` | Add/remove emoji reaction | [L60](../Shared/Model/AppAPITypes.swift#L61) |
+| `apiGetReactionMembers` | `userId, groupId, itemId, reaction` | Get who reacted | [L61](../Shared/Model/AppAPITypes.swift#L62) |
+| `apiPlanForwardChatItems` | `fromChatType, fromChatId, fromScope, itemIds` | Plan message forwarding | [L62](../Shared/Model/AppAPITypes.swift#L63) |
+| `apiForwardChatItems` | `toChatType, toChatId, toScope, from..., itemIds, ttl` | Forward messages | [L63](../Shared/Model/AppAPITypes.swift#L64) |
+| `apiReportMessage` | `groupId, chatItemId, reportReason, reportText` | Report group message | [L54](../Shared/Model/AppAPITypes.swift#L55) |
+| `apiChatRead` | `type, id, scope` | Mark entire chat as read | [L163](../Shared/Model/AppAPITypes.swift#L164) |
+| `apiChatItemsRead` | `type, id, scope, itemIds` | Mark specific items as read | [L164](../Shared/Model/AppAPITypes.swift#L165) |
+| `apiChatUnread` | `type, id, unreadChat` | Toggle unread badge | [L165](../Shared/Model/AppAPITypes.swift#L166) |
+
+### 2.4 Contact Management
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `apiAddContact` | `userId, incognito` | Create invitation link | [L123](../Shared/Model/AppAPITypes.swift#L124) |
+| `apiConnect` | `userId, incognito, connLink` | Connect via link | [L133](../Shared/Model/AppAPITypes.swift#L134) |
+| `apiConnectPlan` | `userId, connLink` | Plan connection (preview) | [L126](../Shared/Model/AppAPITypes.swift#L127) |
+| `apiPrepareContact` | `userId, connLink, contactShortLinkData` | Prepare contact from link | [L127](../Shared/Model/AppAPITypes.swift#L128) |
+| `apiConnectPreparedContact` | `contactId, incognito, msg` | Connect prepared contact | [L131](../Shared/Model/AppAPITypes.swift#L132) |
+| `apiConnectContactViaAddress` | `userId, incognito, contactId` | Connect via address | [L134](../Shared/Model/AppAPITypes.swift#L135) |
+| `apiAcceptContact` | `incognito, contactReqId` | Accept contact request | [L151](../Shared/Model/AppAPITypes.swift#L152) |
+| `apiRejectContact` | `contactReqId` | Reject contact request | [L152](../Shared/Model/AppAPITypes.swift#L153) |
+| `apiDeleteChat` | `type, id, chatDeleteMode` | Delete conversation | [L135](../Shared/Model/AppAPITypes.swift#L136) |
+| `apiClearChat` | `type, id` | Clear conversation history | [L136](../Shared/Model/AppAPITypes.swift#L137) |
+| `apiListContacts` | `userId` | List all contacts | [L137](../Shared/Model/AppAPITypes.swift#L138) |
+| `apiSetContactPrefs` | `contactId, preferences` | Set contact preferences | [L139](../Shared/Model/AppAPITypes.swift#L140) |
+| `apiSetContactAlias` | `contactId, localAlias` | Set local alias | [L140](../Shared/Model/AppAPITypes.swift#L141) |
+| `apiSetConnectionAlias` | `connId, localAlias` | Set pending connection alias | [L142](../Shared/Model/AppAPITypes.swift#L143) |
+| `apiContactInfo` | `contactId` | Get contact info + connection stats | [L109](../Shared/Model/AppAPITypes.swift#L110) |
+| `apiSetConnectionIncognito` | `connId, incognito` | Toggle incognito on pending connection | [L124](../Shared/Model/AppAPITypes.swift#L125) |
+
+### 2.5 Group Management
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `apiNewGroup` | `userId, incognito, groupProfile` | Create new group | [L71](../Shared/Model/AppAPITypes.swift#L72) |
+| `apiAddMember` | `groupId, contactId, memberRole` | Invite contact to group | [L72](../Shared/Model/AppAPITypes.swift#L73) |
+| `apiJoinGroup` | `groupId` | Accept group invitation | [L73](../Shared/Model/AppAPITypes.swift#L74) |
+| `apiAcceptMember` | `groupId, groupMemberId, memberRole` | Accept member (knocking) | [L74](../Shared/Model/AppAPITypes.swift#L75) |
+| `apiRemoveMembers` | `groupId, memberIds, withMessages` | Remove members | [L78](../Shared/Model/AppAPITypes.swift#L79) |
+| `apiLeaveGroup` | `groupId` | Leave group | [L79](../Shared/Model/AppAPITypes.swift#L80) |
+| `apiListMembers` | `groupId` | List group members | [L80](../Shared/Model/AppAPITypes.swift#L81) |
+| `apiUpdateGroupProfile` | `groupId, groupProfile` | Update group name/image/description | [L81](../Shared/Model/AppAPITypes.swift#L82) |
+| `apiMembersRole` | `groupId, memberIds, memberRole` | Change member roles | [L76](../Shared/Model/AppAPITypes.swift#L77) |
+| `apiBlockMembersForAll` | `groupId, memberIds, blocked` | Block members for all | [L77](../Shared/Model/AppAPITypes.swift#L78) |
+| `apiCreateGroupLink` | `groupId, memberRole` | Create shareable group link | [L82](../Shared/Model/AppAPITypes.swift#L83) |
+| `apiGroupLinkMemberRole` | `groupId, memberRole` | Change group link default role | [L83](../Shared/Model/AppAPITypes.swift#L84) |
+| `apiDeleteGroupLink` | `groupId` | Delete group link | [L84](../Shared/Model/AppAPITypes.swift#L85) |
+| `apiGetGroupLink` | `groupId` | Get existing group link | [L85](../Shared/Model/AppAPITypes.swift#L86) |
+| `apiAddGroupShortLink` | `groupId` | Add short link to group | [L86](../Shared/Model/AppAPITypes.swift#L87) |
+| `apiCreateMemberContact` | `groupId, groupMemberId` | Create direct contact from group member | [L87](../Shared/Model/AppAPITypes.swift#L88) |
+| `apiSendMemberContactInvitation` | `contactId, msg` | Send contact invitation to member | [L88](../Shared/Model/AppAPITypes.swift#L89) |
+| `apiGroupMemberInfo` | `groupId, groupMemberId` | Get member info + connection stats | [L110](../Shared/Model/AppAPITypes.swift#L111) |
+| `apiDeleteMemberSupportChat` | `groupId, groupMemberId` | Delete member support chat | [L75](../Shared/Model/AppAPITypes.swift#L76) |
+| `apiSetMemberSettings` | `groupId, groupMemberId, memberSettings` | Set per-member settings | [L108](../Shared/Model/AppAPITypes.swift#L109) |
+| `apiSetGroupAlias` | `groupId, localAlias` | Set local group alias | [L141](../Shared/Model/AppAPITypes.swift#L142) |
+
+### 2.6 Chat Tags
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `apiGetChatTags` | `userId` | Get all user tags | [L42](../Shared/Model/AppAPITypes.swift#L43) |
+| `apiCreateChatTag` | `tag: ChatTagData` | Create a new tag | [L48](../Shared/Model/AppAPITypes.swift#L49) |
+| `apiSetChatTags` | `type, id, tagIds` | Assign tags to a chat | [L49](../Shared/Model/AppAPITypes.swift#L50) |
+| `apiDeleteChatTag` | `tagId` | Delete a tag | [L50](../Shared/Model/AppAPITypes.swift#L51) |
+| `apiUpdateChatTag` | `tagId, tagData` | Update tag name/emoji | [L51](../Shared/Model/AppAPITypes.swift#L52) |
+| `apiReorderChatTags` | `tagIds` | Reorder tags | [L52](../Shared/Model/AppAPITypes.swift#L53) |
+
+### 2.7 File Operations
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `receiveFile` | `fileId, userApprovedRelays, encrypted, inline` | Accept and download file | [L166](../Shared/Model/AppAPITypes.swift#L167) |
+| `setFileToReceive` | `fileId, userApprovedRelays, encrypted` | Mark file for auto-receive | [L167](../Shared/Model/AppAPITypes.swift#L168) |
+| `cancelFile` | `fileId` | Cancel file transfer | [L168](../Shared/Model/AppAPITypes.swift#L169) |
+| `apiUploadStandaloneFile` | `userId, file: CryptoFile` | Upload file to XFTP (no chat) | [L178](../Shared/Model/AppAPITypes.swift#L179) |
+| `apiDownloadStandaloneFile` | `userId, url, file: CryptoFile` | Download from XFTP URL | [L179](../Shared/Model/AppAPITypes.swift#L180) |
+| `apiStandaloneFileInfo` | `url` | Get file metadata from XFTP URL | [L180](../Shared/Model/AppAPITypes.swift#L181) |
+
+### 2.8 WebRTC Call Operations
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `apiSendCallInvitation` | `contact, callType` | Initiate call | [L154](../Shared/Model/AppAPITypes.swift#L155) |
+| `apiRejectCall` | `contact` | Reject incoming call | [L155](../Shared/Model/AppAPITypes.swift#L156) |
+| `apiSendCallOffer` | `contact, callOffer: WebRTCCallOffer` | Send SDP offer | [L156](../Shared/Model/AppAPITypes.swift#L157) |
+| `apiSendCallAnswer` | `contact, answer: WebRTCSession` | Send SDP answer | [L157](../Shared/Model/AppAPITypes.swift#L158) |
+| `apiSendCallExtraInfo` | `contact, extraInfo: WebRTCExtraInfo` | Send ICE candidates | [L158](../Shared/Model/AppAPITypes.swift#L159) |
+| `apiEndCall` | `contact` | End active call | [L159](../Shared/Model/AppAPITypes.swift#L160) |
+| `apiGetCallInvitations` | -- | Get pending call invitations | [L160](../Shared/Model/AppAPITypes.swift#L161) |
+| `apiCallStatus` | `contact, callStatus` | Report call status change | [L161](../Shared/Model/AppAPITypes.swift#L162) |
+
+### 2.9 Push Notifications
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `apiGetNtfToken` | -- | Get current notification token | [L64](../Shared/Model/AppAPITypes.swift#L65) |
+| `apiRegisterToken` | `token, notificationMode` | Register device token with server | [L65](../Shared/Model/AppAPITypes.swift#L66) |
+| `apiVerifyToken` | `token, nonce, code` | Verify token registration | [L66](../Shared/Model/AppAPITypes.swift#L67) |
+| `apiCheckToken` | `token` | Check token status | [L67](../Shared/Model/AppAPITypes.swift#L68) |
+| `apiDeleteToken` | `token` | Unregister token | [L68](../Shared/Model/AppAPITypes.swift#L69) |
+| `apiGetNtfConns` | `nonce, encNtfInfo` | Get notification connections (NSE) | [L69](../Shared/Model/AppAPITypes.swift#L70) |
+| `apiGetConnNtfMessages` | `connMsgReqs` | Get notification messages (NSE) | [L70](../Shared/Model/AppAPITypes.swift#L71) |
+
+### 2.10 Settings & Configuration
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `apiSaveSettings` | `settings: AppSettings` | Save app settings to core | [L40](../Shared/Model/AppAPITypes.swift#L41) |
+| `apiGetSettings` | `settings: AppSettings` | Get settings from core | [L41](../Shared/Model/AppAPITypes.swift#L42) |
+| `apiSetChatSettings` | `type, id, chatSettings` | Per-chat notification settings | [L107](../Shared/Model/AppAPITypes.swift#L108) |
+| `apiSetChatItemTTL` | `userId, seconds` | Set global message TTL | [L99](../Shared/Model/AppAPITypes.swift#L100) |
+| `apiGetChatItemTTL` | `userId` | Get global message TTL | [L100](../Shared/Model/AppAPITypes.swift#L101) |
+| `apiSetChatTTL` | `userId, type, id, seconds` | Per-chat message TTL | [L101](../Shared/Model/AppAPITypes.swift#L102) |
+| `apiSetNetworkConfig` | `networkConfig: NetCfg` | Set network configuration | [L102](../Shared/Model/AppAPITypes.swift#L103) |
+| `apiGetNetworkConfig` | -- | Get network configuration | [L103](../Shared/Model/AppAPITypes.swift#L104) |
+| `apiSetNetworkInfo` | `networkInfo: UserNetworkInfo` | Set network type/status | [L104](../Shared/Model/AppAPITypes.swift#L105) |
+| `reconnectAllServers` | -- | Force reconnect all servers | [L105](../Shared/Model/AppAPITypes.swift#L106) |
+| `reconnectServer` | `userId, smpServer` | Reconnect specific server | [L106](../Shared/Model/AppAPITypes.swift#L107) |
+
+### 2.11 Database & Storage
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `apiStorageEncryption` | `config: DBEncryptionConfig` | Set/change database encryption | [L38](../Shared/Model/AppAPITypes.swift#L39) |
+| `testStorageEncryption` | `key: String` | Test encryption key | [L39](../Shared/Model/AppAPITypes.swift#L40) |
+| `apiExportArchive` | `config: ArchiveConfig` | Export database archive | [L35](../Shared/Model/AppAPITypes.swift#L36) |
+| `apiImportArchive` | `config: ArchiveConfig` | Import database archive | [L36](../Shared/Model/AppAPITypes.swift#L37) |
+| `apiDeleteStorage` | -- | Delete all storage | [L37](../Shared/Model/AppAPITypes.swift#L38) |
+
+### 2.12 Server Operations
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `apiGetServerOperators` | -- | Get server operators | [L91](../Shared/Model/AppAPITypes.swift#L92) |
+| `apiSetServerOperators` | `operators` | Set server operators | [L92](../Shared/Model/AppAPITypes.swift#L93) |
+| `apiGetUserServers` | `userId` | Get user's configured servers | [L93](../Shared/Model/AppAPITypes.swift#L94) |
+| `apiSetUserServers` | `userId, userServers` | Set user's servers | [L94](../Shared/Model/AppAPITypes.swift#L95) |
+| `apiValidateServers` | `userId, userServers` | Validate server configuration | [L95](../Shared/Model/AppAPITypes.swift#L96) |
+| `apiGetUsageConditions` | -- | Get usage conditions | [L96](../Shared/Model/AppAPITypes.swift#L97) |
+| `apiAcceptConditions` | `conditionsId, operatorIds` | Accept usage conditions | [L98](../Shared/Model/AppAPITypes.swift#L99) |
+| `apiTestProtoServer` | `userId, server` | Test server connectivity | [L90](../Shared/Model/AppAPITypes.swift#L91) |
+
+### 2.13 Theme & UI
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `apiSetUserUIThemes` | `userId, themes: ThemeModeOverrides?` | Set per-user theme | [L143](../Shared/Model/AppAPITypes.swift#L144) |
+| `apiSetChatUIThemes` | `chatId, themes: ThemeModeOverrides?` | Set per-chat theme | [L144](../Shared/Model/AppAPITypes.swift#L145) |
+
+### 2.14 Remote Desktop
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `setLocalDeviceName` | `displayName` | Set device name for pairing | [L170](../Shared/Model/AppAPITypes.swift#L171) |
+| `connectRemoteCtrl` | `xrcpInvitation` | Connect to desktop via QR code | [L171](../Shared/Model/AppAPITypes.swift#L172) |
+| `findKnownRemoteCtrl` | -- | Find previously paired desktops | [L172](../Shared/Model/AppAPITypes.swift#L173) |
+| `confirmRemoteCtrl` | `remoteCtrlId` | Confirm known remote controller | [L173](../Shared/Model/AppAPITypes.swift#L174) |
+| `verifyRemoteCtrlSession` | `sessionCode` | Verify session code | [L174](../Shared/Model/AppAPITypes.swift#L175) |
+| `listRemoteCtrls` | -- | List known remote controllers | [L175](../Shared/Model/AppAPITypes.swift#L176) |
+| `stopRemoteCtrl` | -- | Stop remote session | [L176](../Shared/Model/AppAPITypes.swift#L177) |
+| `deleteRemoteCtrl` | `remoteCtrlId` | Delete known controller | [L177](../Shared/Model/AppAPITypes.swift#L178) |
+
+### 2.15 Diagnostics
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `showVersion` | -- | Get core version info | [L182](../Shared/Model/AppAPITypes.swift#L183) |
+| `getAgentSubsTotal` | `userId` | Get total SMP subscriptions | [L183](../Shared/Model/AppAPITypes.swift#L184) |
+| `getAgentServersSummary` | `userId` | Get server summary stats | [L184](../Shared/Model/AppAPITypes.swift#L185) |
+| `resetAgentServersStats` | -- | Reset server statistics | [L185](../Shared/Model/AppAPITypes.swift#L186) |
+
+### 2.16 Address Management
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `apiCreateMyAddress` | `userId` | Create SimpleX address | [L145](../Shared/Model/AppAPITypes.swift#L146) |
+| `apiDeleteMyAddress` | `userId` | Delete SimpleX address | [L146](../Shared/Model/AppAPITypes.swift#L147) |
+| `apiShowMyAddress` | `userId` | Show current address | [L147](../Shared/Model/AppAPITypes.swift#L148) |
+| `apiAddMyAddressShortLink` | `userId` | Add short link to address | [L148](../Shared/Model/AppAPITypes.swift#L149) |
+| `apiSetProfileAddress` | `userId, on: Bool` | Toggle address in profile | [L149](../Shared/Model/AppAPITypes.swift#L150) |
+| `apiSetAddressSettings` | `userId, addressSettings` | Configure address settings | [L150](../Shared/Model/AppAPITypes.swift#L151) |
+
+### 2.17 Connection Security
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `apiGetContactCode` | `contactId` | Get verification code | [L119](../Shared/Model/AppAPITypes.swift#L120) |
+| `apiGetGroupMemberCode` | `groupId, groupMemberId` | Get member verification code | [L120](../Shared/Model/AppAPITypes.swift#L121) |
+| `apiVerifyContact` | `contactId, connectionCode` | Verify contact identity | [L121](../Shared/Model/AppAPITypes.swift#L122) |
+| `apiVerifyGroupMember` | `groupId, groupMemberId, connectionCode` | Verify group member identity | [L122](../Shared/Model/AppAPITypes.swift#L123) |
+| `apiSwitchContact` | `contactId` | Switch contact connection (key rotation) | [L113](../Shared/Model/AppAPITypes.swift#L114) |
+| `apiSwitchGroupMember` | `groupId, groupMemberId` | Switch group member connection | [L114](../Shared/Model/AppAPITypes.swift#L115) |
+| `apiAbortSwitchContact` | `contactId` | Abort contact switch | [L115](../Shared/Model/AppAPITypes.swift#L116) |
+| `apiAbortSwitchGroupMember` | `groupId, groupMemberId` | Abort member switch | [L116](../Shared/Model/AppAPITypes.swift#L117) |
+| `apiSyncContactRatchet` | `contactId, force` | Sync double-ratchet state | [L117](../Shared/Model/AppAPITypes.swift#L118) |
+| `apiSyncGroupMemberRatchet` | `groupId, groupMemberId, force` | Sync member ratchet | [L118](../Shared/Model/AppAPITypes.swift#L119) |
+
+---
+
+## 3. Response Types
+
+Responses are split across three enums due to Swift enum size limitations:
+
+### ChatResponse0
+
+Synchronous query responses ([`AppAPITypes.swift` L647](../Shared/Model/AppAPITypes.swift#L649)):
+
+| Response | Key Fields | Description | Source |
+|----------|-----------|-------------|--------|
+| `activeUser` | `user: User` | Current active user | [L648](../Shared/Model/AppAPITypes.swift#L650) |
+| `usersList` | `users: [UserInfo]` | All user profiles | [L649](../Shared/Model/AppAPITypes.swift#L651) |
+| `chatStarted` | -- | Chat engine started | [L650](../Shared/Model/AppAPITypes.swift#L652) |
+| `chatRunning` | -- | Chat is already running | [L651](../Shared/Model/AppAPITypes.swift#L653) |
+| `chatStopped` | -- | Chat engine stopped | [L652](../Shared/Model/AppAPITypes.swift#L654) |
+| `apiChats` | `user, chats: [ChatData]` | All chat previews | [L653](../Shared/Model/AppAPITypes.swift#L655) |
+| `apiChat` | `user, chat: ChatData, navInfo` | Single chat with messages | [L654](../Shared/Model/AppAPITypes.swift#L656) |
+| `chatTags` | `user, userTags: [ChatTag]` | User's chat tags | [L656](../Shared/Model/AppAPITypes.swift#L658) |
+| `chatItemInfo` | `user, chatItem, chatItemInfo` | Message detail info | [L657](../Shared/Model/AppAPITypes.swift#L659) |
+| `serverTestResult` | `user, testServer, testFailure` | Server test result | [L658](../Shared/Model/AppAPITypes.swift#L660) |
+| `networkConfig` | `networkConfig: NetCfg` | Current network config | [L664](../Shared/Model/AppAPITypes.swift#L666) |
+| `contactInfo` | `user, contact, connectionStats, customUserProfile` | Contact details | [L665](../Shared/Model/AppAPITypes.swift#L667) |
+| `groupMemberInfo` | `user, groupInfo, member, connectionStats` | Member details | [L666](../Shared/Model/AppAPITypes.swift#L668) |
+| `connectionVerified` | `verified, expectedCode` | Verification result | [L676](../Shared/Model/AppAPITypes.swift#L678) |
+| `tagsUpdated` | `user, userTags, chatTags` | Tags changed | [L677](../Shared/Model/AppAPITypes.swift#L679) |
+
+### ChatResponse1
+
+Contact, message, and profile responses ([`AppAPITypes.swift` L768](../Shared/Model/AppAPITypes.swift#L771)):
+
+| Response | Key Fields | Description | Source |
+|----------|-----------|-------------|--------|
+| `invitation` | `user, connLinkInvitation, connection` | Created invitation link | [L769](../Shared/Model/AppAPITypes.swift#L772) |
+| `connectionPlan` | `user, connLink, connectionPlan` | Connection plan preview | [L772](../Shared/Model/AppAPITypes.swift#L775) |
+| `newPreparedChat` | `user, chat: ChatData` | Prepared contact/group | [L773](../Shared/Model/AppAPITypes.swift#L776) |
+| `contactDeleted` | `user, contact` | Contact deleted | [L782](../Shared/Model/AppAPITypes.swift#L785) |
+| `newChatItems` | `user, chatItems: [AChatItem]` | New messages sent/received | [L800](../Shared/Model/AppAPITypes.swift#L803) |
+| `chatItemUpdated` | `user, chatItem: AChatItem` | Message edited | [L803](../Shared/Model/AppAPITypes.swift#L806) |
+| `chatItemReaction` | `user, added, reaction` | Reaction change | [L805](../Shared/Model/AppAPITypes.swift#L808) |
+| `chatItemsDeleted` | `user, chatItemDeletions, byUser` | Messages deleted | [L807](../Shared/Model/AppAPITypes.swift#L810) |
+| `contactsList` | `user, contacts: [Contact]` | All contacts list | [L808](../Shared/Model/AppAPITypes.swift#L811) |
+| `userProfileUpdated` | `user, fromProfile, toProfile` | Profile changed | [L788](../Shared/Model/AppAPITypes.swift#L791) |
+| `userContactLinkCreated` | `user, connLinkContact` | Address created | [L796](../Shared/Model/AppAPITypes.swift#L799) |
+| `forwardPlan` | `user, chatItemIds, forwardConfirmation` | Forward plan result | [L802](../Shared/Model/AppAPITypes.swift#L805) |
+| `groupChatItemsDeleted` | `user, groupInfo, chatItemIDs, byUser, member_` | Group items deleted | [L801](../Shared/Model/AppAPITypes.swift#L804) |
+
+### ChatResponse2
+
+Group, file, call, notification, and misc responses ([`AppAPITypes.swift` L907](../Shared/Model/AppAPITypes.swift#L911)):
+
+| Response | Key Fields | Description | Source |
+|----------|-----------|-------------|--------|
+| `groupCreated` | `user, groupInfo` | New group created | [L909](../Shared/Model/AppAPITypes.swift#L913) |
+| `sentGroupInvitation` | `user, groupInfo, contact, member` | Group invitation sent | [L910](../Shared/Model/AppAPITypes.swift#L914) |
+| `groupMembers` | `user, group: Group` | Group member list | [L914](../Shared/Model/AppAPITypes.swift#L918) |
+| `membersRoleUser` | `user, groupInfo, members, toRole` | Role changed | [L918](../Shared/Model/AppAPITypes.swift#L922) |
+| `groupUpdated` | `user, toGroup: GroupInfo` | Group profile updated | [L920](../Shared/Model/AppAPITypes.swift#L924) |
+| `groupLinkCreated` | `user, groupInfo, groupLink` | Group link created | [L921](../Shared/Model/AppAPITypes.swift#L925) |
+| `rcvFileAccepted` | `user, chatItem` | File download started | [L928](../Shared/Model/AppAPITypes.swift#L932) |
+| `callInvitations` | `callInvitations: [RcvCallInvitation]` | Pending calls | [L937](../Shared/Model/AppAPITypes.swift#L941) |
+| `ntfToken` | `token, status, ntfMode, ntfServer` | Notification token info | [L940](../Shared/Model/AppAPITypes.swift#L944) |
+| `versionInfo` | `versionInfo, chatMigrations, agentMigrations` | Core version | [L948](../Shared/Model/AppAPITypes.swift#L952) |
+| `cmdOk` | `user_` | Generic success | [L949](../Shared/Model/AppAPITypes.swift#L953) |
+| `archiveExported` | `archiveErrors: [ArchiveError]` | Export result | [L953](../Shared/Model/AppAPITypes.swift#L957) |
+| `archiveImported` | `archiveErrors: [ArchiveError]` | Import result | [L954](../Shared/Model/AppAPITypes.swift#L958) |
+| `appSettings` | `appSettings: AppSettings` | Retrieved settings | [L955](../Shared/Model/AppAPITypes.swift#L959) |
+
+---
+
+## 4. Event Types
+
+The `ChatEvent` enum ([`AppAPITypes.swift` L1050](../Shared/Model/AppAPITypes.swift#L1055)) 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#L2266) in `SimpleXAPI.swift`.
+
+### Connection Events
+
+| Event | Key Fields | Description | Source |
+|-------|-----------|-------------|--------|
+| `contactConnected` | `user, contact, userCustomProfile` | Contact connection established | [L1057](../Shared/Model/AppAPITypes.swift#L1062) |
+| `contactConnecting` | `user, contact` | Contact connecting in progress | [L1058](../Shared/Model/AppAPITypes.swift#L1063) |
+| `contactSndReady` | `user, contact` | Ready to send to contact | [L1059](../Shared/Model/AppAPITypes.swift#L1064) |
+| `contactDeletedByContact` | `user, contact` | Contact deleted by other party | [L1056](../Shared/Model/AppAPITypes.swift#L1061) |
+| `contactUpdated` | `user, toContact` | Contact profile updated | [L1061](../Shared/Model/AppAPITypes.swift#L1066) |
+| `receivedContactRequest` | `user, contactRequest, chat_` | Incoming contact request | [L1060](../Shared/Model/AppAPITypes.swift#L1065) |
+| `subscriptionStatus` | `subscriptionStatus, connections` | Connection subscription change | [L1063](../Shared/Model/AppAPITypes.swift#L1068) |
+
+### Message Events
+
+| Event | Key Fields | Description | Source |
+|-------|-----------|-------------|--------|
+| `newChatItems` | `user, chatItems: [AChatItem]` | New messages received | [L1065](../Shared/Model/AppAPITypes.swift#L1070) |
+| `chatItemUpdated` | `user, chatItem: AChatItem` | Message edited remotely | [L1067](../Shared/Model/AppAPITypes.swift#L1072) |
+| `chatItemReaction` | `user, added, reaction: ACIReaction` | Reaction added/removed | [L1068](../Shared/Model/AppAPITypes.swift#L1073) |
+| `chatItemsDeleted` | `user, chatItemDeletions, byUser` | Messages deleted | [L1069](../Shared/Model/AppAPITypes.swift#L1074) |
+| `chatItemsStatusesUpdated` | `user, chatItems: [AChatItem]` | Delivery status changed | [L1066](../Shared/Model/AppAPITypes.swift#L1071) |
+| `groupChatItemsDeleted` | `user, groupInfo, chatItemIDs, byUser, member_` | Group items deleted | [L1071](../Shared/Model/AppAPITypes.swift#L1076) |
+| `chatInfoUpdated` | `user, chatInfo` | Chat metadata changed | [L1064](../Shared/Model/AppAPITypes.swift#L1069) |
+
+### Group Events
+
+| Event | Key Fields | Description | Source |
+|-------|-----------|-------------|--------|
+| `receivedGroupInvitation` | `user, groupInfo, contact, memberRole` | Group invitation received | [L1072](../Shared/Model/AppAPITypes.swift#L1077) |
+| `userAcceptedGroupSent` | `user, groupInfo, hostContact` | Joined group | [L1073](../Shared/Model/AppAPITypes.swift#L1078) |
+| `groupLinkConnecting` | `user, groupInfo, hostMember` | Connecting via group link | [L1074](../Shared/Model/AppAPITypes.swift#L1079) |
+| `joinedGroupMemberConnecting` | `user, groupInfo, hostMember, member` | Member joining | [L1076](../Shared/Model/AppAPITypes.swift#L1081) |
+| `memberRole` | `user, groupInfo, byMember, member, fromRole, toRole` | Role changed | [L1078](../Shared/Model/AppAPITypes.swift#L1083) |
+| `memberBlockedForAll` | `user, groupInfo, byMember, member, blocked` | Member blocked | [L1079](../Shared/Model/AppAPITypes.swift#L1084) |
+| `deletedMemberUser` | `user, groupInfo, member, withMessages` | Current user removed | [L1080](../Shared/Model/AppAPITypes.swift#L1085) |
+| `deletedMember` | `user, groupInfo, byMember, deletedMember` | Member removed | [L1081](../Shared/Model/AppAPITypes.swift#L1086) |
+| `leftMember` | `user, groupInfo, member` | Member left | [L1082](../Shared/Model/AppAPITypes.swift#L1087) |
+| `groupDeleted` | `user, groupInfo, member` | Group deleted | [L1083](../Shared/Model/AppAPITypes.swift#L1088) |
+| `userJoinedGroup` | `user, groupInfo` | Successfully joined | [L1084](../Shared/Model/AppAPITypes.swift#L1089) |
+| `joinedGroupMember` | `user, groupInfo, member` | New member joined | [L1085](../Shared/Model/AppAPITypes.swift#L1090) |
+| `connectedToGroupMember` | `user, groupInfo, member, memberContact` | E2E session established with member | [L1086](../Shared/Model/AppAPITypes.swift#L1091) |
+| `groupUpdated` | `user, toGroup: GroupInfo` | Group profile changed | [L1087](../Shared/Model/AppAPITypes.swift#L1092) |
+| `groupMemberUpdated` | `user, groupInfo, fromMember, toMember` | Member info updated | [L1062](../Shared/Model/AppAPITypes.swift#L1067) |
+
+### File Transfer Events
+
+| Event | Key Fields | Description | Source |
+|-------|-----------|-------------|--------|
+| `rcvFileStart` | `user, chatItem` | Download started | [L1092](../Shared/Model/AppAPITypes.swift#L1097) |
+| `rcvFileProgressXFTP` | `user, chatItem_, receivedSize, totalSize` | Download progress | [L1093](../Shared/Model/AppAPITypes.swift#L1098) |
+| `rcvFileComplete` | `user, chatItem` | Download complete | [L1094](../Shared/Model/AppAPITypes.swift#L1099) |
+| `rcvFileSndCancelled` | `user, chatItem, rcvFileTransfer` | Sender cancelled | [L1096](../Shared/Model/AppAPITypes.swift#L1101) |
+| `rcvFileError` | `user, chatItem_, agentError, rcvFileTransfer` | Download error | [L1097](../Shared/Model/AppAPITypes.swift#L1102) |
+| `sndFileStart` | `user, chatItem, sndFileTransfer` | Upload started | [L1100](../Shared/Model/AppAPITypes.swift#L1105) |
+| `sndFileComplete` | `user, chatItem, sndFileTransfer` | Upload complete (inline) | [L1101](../Shared/Model/AppAPITypes.swift#L1106) |
+| `sndFileProgressXFTP` | `user, chatItem_, fileTransferMeta, sentSize, totalSize` | Upload progress | [L1103](../Shared/Model/AppAPITypes.swift#L1108) |
+| `sndFileCompleteXFTP` | `user, chatItem, fileTransferMeta` | XFTP upload complete | [L1105](../Shared/Model/AppAPITypes.swift#L1110) |
+| `sndFileError` | `user, chatItem_, fileTransferMeta, errorMessage` | Upload error | [L1107](../Shared/Model/AppAPITypes.swift#L1112) |
+
+### Call Events
+
+| Event | Key Fields | Description | Source |
+|-------|-----------|-------------|--------|
+| `callInvitation` | `callInvitation: RcvCallInvitation` | Incoming call | [L1110](../Shared/Model/AppAPITypes.swift#L1115) |
+| `callOffer` | `user, contact, callType, offer, sharedKey, askConfirmation` | SDP offer received | [L1111](../Shared/Model/AppAPITypes.swift#L1116) |
+| `callAnswer` | `user, contact, answer` | SDP answer received | [L1112](../Shared/Model/AppAPITypes.swift#L1117) |
+| `callExtraInfo` | `user, contact, extraInfo` | ICE candidates received | [L1113](../Shared/Model/AppAPITypes.swift#L1118) |
+| `callEnded` | `user, contact` | Call ended by remote | [L1114](../Shared/Model/AppAPITypes.swift#L1119) |
+
+### Connection Security Events
+
+| Event | Key Fields | Description | Source |
+|-------|-----------|-------------|--------|
+| `contactSwitch` | `user, contact, switchProgress` | Key rotation progress | [L1052](../Shared/Model/AppAPITypes.swift#L1057) |
+| `groupMemberSwitch` | `user, groupInfo, member, switchProgress` | Member key rotation | [L1053](../Shared/Model/AppAPITypes.swift#L1058) |
+| `contactRatchetSync` | `user, contact, ratchetSyncProgress` | Ratchet sync progress | [L1054](../Shared/Model/AppAPITypes.swift#L1059) |
+| `groupMemberRatchetSync` | `user, groupInfo, member, ratchetSyncProgress` | Member ratchet sync | [L1055](../Shared/Model/AppAPITypes.swift#L1060) |
+
+### System Events
+
+| Event | Key Fields | Description | Source |
+|-------|-----------|-------------|--------|
+| `chatSuspended` | -- | Core suspended | [L1051](../Shared/Model/AppAPITypes.swift#L1056) |
+
+---
+
+## 5. Error Types
+
+Defined in [`SimpleXChat/APITypes.swift` L695](../SimpleXChat/APITypes.swift#L699):
+
+```swift
+public enum ChatError: Decodable, Hashable {
+ case error(errorType: ChatErrorType)
+ case errorAgent(agentError: AgentErrorType)
+ case errorStore(storeError: StoreError)
+ case errorDatabase(databaseError: DatabaseError)
+ case errorRemoteCtrl(remoteCtrlError: RemoteCtrlError)
+ case invalidJSON(json: String)
+ case unexpectedResult(type: String)
+}
+```
+
+### Error Categories
+
+| Category | Enum | Description | Source |
+|----------|------|-------------|--------|
+| Chat logic | `ChatErrorType` | Business logic errors (e.g., invalid state, permission denied) | [`APITypes.swift` L717](../SimpleXChat/APITypes.swift#L722) |
+| SMP Agent | `AgentErrorType` | Protocol/network errors from the SMP agent layer | [`APITypes.swift` L873](../SimpleXChat/APITypes.swift#L878) |
+| Database store | `StoreError` | SQLite query/constraint errors | [`APITypes.swift` L796](../SimpleXChat/APITypes.swift#L801) |
+| Database engine | `DatabaseError` | DB open/migration/encryption errors | [`APITypes.swift` L860](../SimpleXChat/APITypes.swift#L865) |
+| Remote control | `RemoteCtrlError` | Remote desktop session errors | [`APITypes.swift` L1043](../SimpleXChat/APITypes.swift#L1048) |
+| Parse failure | `invalidJSON` | Failed to decode response JSON | [`APITypes.swift` L695](../SimpleXChat/APITypes.swift#L699) |
+| Unexpected | `unexpectedResult` | Response type does not match expected | [`APITypes.swift` L695](../SimpleXChat/APITypes.swift#L699) |
+
+---
+
+## 6. FFI Bridge Functions
+
+Defined in [`Shared/Model/SimpleXAPI.swift`](../Shared/Model/SimpleXAPI.swift):
+
+### Synchronous (blocking current thread)
+
+```swift
+// Throws on error, returns typed result
+func chatSendCmdSync( // SimpleXAPI.swift L91
+ _ cmd: ChatCommand,
+ bgTask: Bool = true,
+ bgDelay: Double? = nil,
+ ctrl: chat_ctrl? = nil,
+ log: Bool = true
+) throws -> R
+
+// Returns APIResult (caller handles error)
+func chatApiSendCmdSync( // SimpleXAPI.swift L96
+ _ cmd: ChatCommand,
+ bgTask: Bool = true,
+ bgDelay: Double? = nil,
+ ctrl: chat_ctrl? = nil,
+ retryNum: Int32 = 0,
+ log: Bool = true
+) -> APIResult
+```
+
+### Asynchronous (Swift concurrency)
+
+```swift
+// Throws on error, returns typed result
+func chatSendCmd( // SimpleXAPI.swift L117
+ _ cmd: ChatCommand,
+ bgTask: Bool = true,
+ bgDelay: Double? = nil,
+ ctrl: chat_ctrl? = nil,
+ log: Bool = true
+) async throws -> R
+
+// Returns APIResult with optional retry on network errors
+func chatApiSendCmdWithRetry( // SimpleXAPI.swift L122
+ _ cmd: ChatCommand,
+ bgTask: Bool = true,
+ bgDelay: Double? = nil,
+ inProgress: BoxedValue? = nil,
+ retryNum: Int32 = 0
+) async -> APIResult?
+```
+
+### Low-Level FFI
+
+```swift
+// Direct C FFI call -- serializes cmd.cmdString, calls chat_send_cmd_retry, decodes response
+public func sendSimpleXCmd( // API.swift L115
+ _ cmd: ChatCmdProtocol,
+ _ ctrl: chat_ctrl?,
+ retryNum: Int32 = 0
+) -> APIResult
+```
+
+### Event Receiver
+
+```swift
+// Polls for async events from the Haskell core
+func chatRecvMsg( // SimpleXAPI.swift L230
+ _ ctrl: chat_ctrl? = nil
+) async -> APIResult?
+
+// Processes a received event and updates app state
+func processReceivedMsg( // SimpleXAPI.swift L2248
+ _ res: ChatEvent
+) async
+```
+
+---
+
+## 7. Result Type
+
+Defined in [`SimpleXChat/APITypes.swift` L26](../SimpleXChat/APITypes.swift#L27):
+
+```swift
+public enum APIResult: Decodable where R: Decodable, R: ChatAPIResult {
+ case result(R) // Successful response
+ case error(ChatError) // Error response from core
+ case invalid(type: String, json: Data) // Undecodable response
+
+ public var responseType: String { ... }
+ public var unexpected: ChatError { ... }
+}
+
+public protocol ChatAPIResult: Decodable { // APITypes.swift L63
+ var responseType: String { get }
+ var details: String { get }
+ static func fallbackResult(_ type: String, _ json: NSDictionary) -> Self?
+}
+```
+
+The `decodeAPIResult` function ([`APITypes.swift` L83](../SimpleXChat/APITypes.swift#L86)) handles JSON decoding with fallback logic:
+1. Try standard `JSONDecoder.decode(APIResult.self, from: data)`
+2. If that fails, try manual JSON parsing via `JSONSerialization`
+3. Check for `"error"` key -- return `.error`
+4. Check for `"result"` key -- try `R.fallbackResult` or return `.invalid`
+5. Last resort: return `.invalid(type: "invalid", json: ...)`
+
+---
+
+## Source Files
+
+| File | Path |
+|------|------|
+| ChatCommand enum | [`Shared/Model/AppAPITypes.swift` L14](../Shared/Model/AppAPITypes.swift#L15) |
+| ChatResponse0/1/2 enums | [`Shared/Model/AppAPITypes.swift` L647, L768, L907](../Shared/Model/AppAPITypes.swift#L649) |
+| ChatEvent enum | [`Shared/Model/AppAPITypes.swift` L1050](../Shared/Model/AppAPITypes.swift#L1055) |
+| APIResult, ChatError | [`SimpleXChat/APITypes.swift` L26, L695](../SimpleXChat/APITypes.swift#L27) |
+| FFI bridge functions | [`Shared/Model/SimpleXAPI.swift`](../Shared/Model/SimpleXAPI.swift) |
+| Low-level FFI | [`SimpleXChat/API.swift`](../SimpleXChat/API.swift) |
+| Data types | `SimpleXChat/ChatTypes.swift` |
+| C header | `SimpleXChat/SimpleX.h` |
+| Haskell controller | `../../src/Simplex/Chat/Controller.hs` |
diff --git a/apps/ios/spec/architecture.md b/apps/ios/spec/architecture.md
new file mode 100644
index 0000000000..84d9d3269d
--- /dev/null
+++ b/apps/ios/spec/architecture.md
@@ -0,0 +1,298 @@
+# SimpleX Chat iOS -- System Architecture
+
+> Technical specification for the iOS app's layered architecture, FFI bridge, event system, and extension model.
+>
+> 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)
+
+---
+
+## Table of Contents
+
+1. [Layered Architecture](#1-layered-architecture)
+2. [FFI Bridge](#2-ffi-bridge)
+3. [Event Streaming](#3-event-streaming)
+4. [Database Architecture](#4-database-architecture)
+5. [App Lifecycle](#5-app-lifecycle)
+6. [Extension Architecture](#6-extension-architecture)
+7. [Remote Desktop Control](#7-remote-desktop-control)
+
+---
+
+## [1. Layered Architecture](../Shared/SimpleXApp.swift#L17-L184)
+
+The app follows a strict layered model where each layer communicates only with its immediate neighbor:
+
+```
+┌─────────────────────────────────────────┐
+│ SwiftUI Views │ Rendering, user interaction
+│ (ChatListView, ChatView, ComposeView) │
+├─────────────────────────────────────────┤
+│ ChatModel (ObservableObject) │ App state, @Published properties
+│ ItemsModel, Chat, ChatTagsModel │ Per-chat state, tag filtering
+├─────────────────────────────────────────┤
+│ SimpleXAPI (FFI Bridge) │ chatSendCmd/chatApiSendCmd
+│ AppAPITypes (ChatCommand/Response) │ JSON serialization/deserialization
+├─────────────────────────────────────────┤
+│ C FFI Layer │ chat_send_cmd_retry, chat_recv_msg_wait
+│ (SimpleX.h, libsimplex.a) │ Compiled Haskell via GHC cross-compiler
+├─────────────────────────────────────────┤
+│ Haskell Core (chat_ctrl) │ Chat logic, chat protocol (x-events),
+│ (Simplex.Chat.Controller) │ database operations, file management
+├─────────────────────────────────────────┤
+│ simplexmq library (external) │ SMP/XFTP protocols, SMP Agent,
+│ (github.com/simplex-chat/simplexmq) │ double-ratchet (PQDR), transport (TLS)
+└─────────────────────────────────────────┘
+```
+
+**Key invariant**: No SwiftUI view directly calls FFI functions. All communication flows through `ChatModel` or dedicated API functions in `SimpleXAPI.swift`.
+
+### Source Files
+
+| Layer | File | Role | Line |
+|-------|------|------|------|
+| 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 |
+| 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 |
+| 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 |
+| Core | `../../src/Simplex/Chat/Controller.hs` | Haskell command processor — see `processCommand` in `Controller.hs` | |
+
+---
+
+## [2. FFI Bridge](../SimpleXChat/SimpleX.h#L1-L49)
+
+### [C Functions (SimpleX.h)](../SimpleXChat/SimpleX.h#L1-L49)
+
+The Haskell core exposes these C functions, declared in `SimpleXChat/SimpleX.h`:
+
+```c
+typedef void* chat_ctrl;
+
+// Initialize database, apply migrations, return controller
+char *chat_migrate_init_key(char *path, char *key, int keepKey, char *confirm,
+ int backgroundMode, chat_ctrl *ctrl);
+
+// Send command string, return JSON response string
+char *chat_send_cmd_retry(chat_ctrl ctl, char *cmd, int retryNum);
+
+// Block until next async event arrives (or timeout)
+char *chat_recv_msg_wait(chat_ctrl ctl, int wait);
+
+// Close/reopen database store
+char *chat_close_store(chat_ctrl ctl);
+char *chat_reopen_store(chat_ctrl ctl);
+
+// Utility: markdown parsing, server validation, password hashing
+char *chat_parse_markdown(char *str);
+char *chat_parse_server(char *str);
+char *chat_password_hash(char *pwd, char *salt);
+
+// File encryption/decryption
+char *chat_write_file(chat_ctrl ctl, char *path, char *data, int len);
+char *chat_read_file(char *path, char *key, char *nonce);
+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
+// Synchronous send -- blocks calling thread
+func chatSendCmdSync(_ cmd: ChatCommand, bgTask: Bool = true,
+ bgDelay: Double? = nil, ctrl: chat_ctrl? = nil) throws -> R // L91
+
+// Async send -- dispatches to background
+func chatApiSendCmd(_ cmd: ChatCommand, bgTask: Bool = true,
+ bgDelay: Double? = nil, ctrl: chat_ctrl? = nil) async -> APIResult // L215
+
+// Low-level FFI call -- serializes command to string, calls chat_send_cmd_retry, decodes JSON
+func sendSimpleXCmd(_ cmd: ChatCmdProtocol, _ ctrl: chat_ctrl?,
+ retryNum: Int32 = 0) -> APIResult // SimpleXChat/API.swift L114
+```
+
+### Data Flow
+
+1. Swift constructs a `ChatCommand` enum value (e.g., `.apiSendMessages(type:id:scope:live:ttl:composedMessages:)`)
+2. [`ChatCommand.cmdString`](../Shared/Model/AppAPITypes.swift#L15) serializes it to a command string (e.g., `"/_send @1 json {...}"`)
+3. [`sendSimpleXCmd`](../SimpleXChat/API.swift#L115) passes the string to `chat_send_cmd_retry` via C FFI
+4. Haskell core processes the command, returns JSON response string
+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)
+
+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)
+
+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:
+
+```
+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).
+
+### [Event Types (ChatEvent enum)](../Shared/Model/AppAPITypes.swift#L1055-L1129)
+
+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) |
+
+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.
+
+---
+
+## [4. Database Architecture](../SimpleXChat/FileUtils.swift#L70-L294)
+
+Two SQLite databases in the app group container (shared with NSE):
+
+| Database | File | Contents |
+|----------|------|----------|
+| Chat DB | `simplex_v1_chat.db` | Messages, contacts, groups, profiles, files, tags, preferences |
+| Agent DB | `simplex_v1_agent.db` | SMP connections, keys, queues, server info |
+
+Both databases use the `DB_FILE_PREFIX = "simplex_v1"` prefix. The database path is resolved via [`getAppDatabasePath()`](../SimpleXChat/FileUtils.swift#L70) in `SimpleXChat/FileUtils.swift`, which checks `dbContainerGroupDefault` to determine whether to use the app group container or legacy documents directory.
+
+See [Database & Storage specification](database.md) for full details.
+
+---
+
+## [5. App Lifecycle](../Shared/SimpleXApp.swift#L17-L184)
+
+### [Initialization Sequence (SimpleXApp.swift)](../Shared/SimpleXApp.swift#L17-L38)
+
+```swift
+// SimpleXApp.init()
+1. haskell_init() // Initialize Haskell RTS (background queue, sync)
+2. UserDefaults.register(defaults:) // Register app preference defaults
+3. setGroupDefaults() // Sync preferences to app group container
+4. setDbContainer() // Set database path L122
+5. BGManager.shared.register() // Register background task handlers
+6. NtfManager.shared.registerCategories() // Register notification action categories
+```
+
+### State Transitions
+
+```
+ ┌──────────┐
+ │ Launched │
+ └─────┬─────┘
+ │ initChatAndMigrate()
+ v
+ ┌──────────┐
+ │ DB Setup │ chat_migrate_init_key()
+ └─────┬─────┘
+ │ startChat() SimpleXAPI.swift L2098
+ v
+ ┌──────────┐
+ │ Active │ apiActivateChat() SimpleXAPI.swift L358
+ └─────┬─────┘
+ │ scenePhase == .background
+ v
+ ┌──────────┐
+ │Background │ apiSuspendChat(timeoutMicroseconds:) SimpleXAPI.swift L368
+ └─────┬─────┘
+ │ scenePhase == .active
+ v
+ ┌──────────┐
+ │ Active │ startChatAndActivate()
+ └──────────┘
+```
+
+### [Scene Phase Handling (SimpleXApp.swift)](../Shared/SimpleXApp.swift#L38-L123)
+
+- **`.active`**: Calls `startChatAndActivate()`, processes pending notification responses, refreshes chat list and call invitations
+- **`.background`**: Records authentication timestamp, calls `suspendChat()` (unless CallKit call active), schedules `BGManager` background refresh, updates badge count
+- **`.inactive`**: No explicit handling (transitional state)
+
+### CallKit Exception
+
+When a CallKit call is active during backgrounding, chat suspension is deferred (`CallController.shared.shouldSuspendChat = true`) until the call ends, to maintain the WebRTC session.
+
+---
+
+## [6. Extension Architecture](../SimpleX%20NSE/NotificationService.swift#L1-L1228)
+
+### [Notification Service Extension (NSE)](../SimpleX%20NSE/NotificationService.swift#L1-L1228)
+
+The NSE ([`SimpleX NSE/NotificationService.swift`](../SimpleX%20NSE/NotificationService.swift#L1-L1228)) is a separate process that:
+
+1. Receives encrypted push notification payload from APNs
+2. Initializes its own Haskell core instance (`chat_ctrl`) with shared database access
+3. Decrypts the push payload using stored keys
+4. Generates a visible `UNMutableNotificationContent` with the decrypted message preview
+5. Delivers the notification to the user
+
+**Database sharing**: Both main app and NSE access the same database files in the app group container (`APP_GROUP_NAME`). Coordination uses file locks to prevent concurrent write conflicts.
+
+**Lifecycle**: The NSE has a ~30-second execution window per notification. It must initialize Haskell RTS, open the database, decrypt, and deliver within this window.
+
+### Share Extension (SE)
+
+The Share Extension (`SimpleX SE/`) allows sharing content (text, images, files) from other apps into SimpleX conversations.
+
+---
+
+## [7. Remote Desktop Control](../Shared/Views/RemoteAccess/ConnectDesktopView.swift#L1-L545)
+
+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
+- **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/`
+
+---
+
+## Source Files
+
+| File | Path | Line |
+|------|------|------|
+| App entry point | [`Shared/SimpleXApp.swift`](../Shared/SimpleXApp.swift#L17) | L17 |
+| App delegate | [`Shared/AppDelegate.swift`](../Shared/AppDelegate.swift#L15) | L15 |
+| 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 |
+| 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) | |
+| NSE | [`SimpleX NSE/NotificationService.swift`](../SimpleX%20NSE/NotificationService.swift#L1-L1228) | |
+| Haskell core | `../../src/Simplex/Chat/Controller.hs` — see `processCommand` in `Controller.hs` | |
+| Chat protocol (x-events, message envelopes) | `../../src/Simplex/Chat/Protocol.hs` | |
+
+### External: simplexmq Library
+
+The lower-level protocol and encryption layers are in the separate [simplexmq](https://github.com/simplex-chat/simplexmq) library:
+
+| Component | Spec | Implementation |
+|-----------|------|----------------|
+| SMP protocol | `simplexmq/protocol/simplex-messaging.md` | `simplexmq/src/Simplex/Messaging/Protocol.hs` |
+| XFTP protocol | `simplexmq/protocol/xftp.md` | `simplexmq/src/Simplex/FileTransfer/Protocol.hs` |
+| SMP Agent (duplex connections) | `simplexmq/protocol/agent-protocol.md` | `simplexmq/src/Simplex/Messaging/Agent.hs` |
+| Double ratchet (PQDR) | `simplexmq/protocol/pqdr.md` | `simplexmq/src/Simplex/Messaging/Crypto/Ratchet.hs` |
+| Post-quantum KEM (sntrup761) | `simplexmq/protocol/pqdr.md` | `simplexmq/src/Simplex/Messaging/Crypto/SNTRUP761.hs` |
+| TLS transport | — | `simplexmq/src/Simplex/Messaging/Transport.hs` |
+| File encryption | — | `simplexmq/src/Simplex/Messaging/Crypto/File.hs` |
diff --git a/apps/ios/spec/client/chat-list.md b/apps/ios/spec/client/chat-list.md
new file mode 100644
index 0000000000..0eb3cd75f7
--- /dev/null
+++ b/apps/ios/spec/client/chat-list.md
@@ -0,0 +1,280 @@
+# SimpleX Chat iOS -- Chat List Module
+
+> Technical specification for the conversation list, filtering, search, swipe actions, and user picker.
+>
+> Related specs: [Chat View](chat-view.md) | [Navigation](navigation.md) | [State Management](../state.md) | [README](../README.md)
+> Related product: [Chat List View](../../product/views/chat-list.md)
+
+**Source:** [`ChatListView.swift`](../../Shared/Views/ChatList/ChatListView.swift)
+
+---
+
+## Table of Contents
+
+1. [Overview](#1-overview)
+2. [ChatListView](#2-chatlistview)
+3. [ChatPreviewView](#3-chatpreviewview)
+4. [ChatListNavLink](#4-chatlistnavlink)
+5. [Filtering & Tags](#5-filtering--tags)
+6. [Search](#6-search)
+7. [Swipe Actions](#7-swipe-actions)
+8. [UserPicker](#8-userpicker)
+9. [Floating Action Button](#9-floating-action-button)
+
+---
+
+## 1. Overview
+
+The chat list is the main screen of the app, displaying all conversations for the current user. It provides:
+
+- Conversation previews with unread badges
+- Filter tabs (All, Unread, Favorites, Groups, Contacts, Business, user-defined tags)
+- Search across chat names and message content
+- Swipe actions for quick operations
+- User profile switcher
+- Floating action button for new conversations
+
+```
+ChatListView
+├── Navigation Bar
+│ ├── User avatar (tap → UserPicker)
+│ └── Filter tabs (TagListView)
+├── Search bar (on pull-down or tap)
+├── Chat List (List/LazyVStack)
+│ └── ChatListNavLink (per conversation)
+│ └── ChatPreviewView
+│ ├── Avatar
+│ ├── Chat name + last message preview
+│ ├── Timestamp
+│ └── Unread badge
+├── FAB (New Chat button)
+└── Pending connection cards
+```
+
+---
+
+## 2. [`ChatListView`](../../Shared/Views/ChatList/ChatListView.swift#L142) {#2-chatlistview}
+
+**File**: `Shared/Views/ChatList/ChatListView.swift`
+
+The root list view. Key responsibilities:
+
+### Data Source
+- Reads `ChatModel.shared.chats` (all conversations)
+- Applies active filter from `ChatTagsModel.shared.activeFilter`
+- Applies search query filtering via [`filteredChats()`](../../Shared/Views/ChatList/ChatListView.swift#L480)
+- Sorts by last activity (most recent first), with pinned chats at top
+
+### Layout
+- Uses SwiftUI `List` with `ForEach` over filtered chats
+- Each row is a `ChatListNavLink` wrapping a `ChatPreviewView`
+- Pull-to-refresh triggers `updateChats()` API call
+- Empty state: `ChatHelp` view with getting-started guidance
+
+### Connection Cards
+- Pending contact connections (`ChatInfo.contactConnection`) shown as cards
+- Contact requests (`ChatInfo.contactRequest`) shown with accept/reject UI via `ContactRequestView`
+
+### Key Functions
+
+| Function | Line | Description |
+|----------|------|-------------|
+| [`body`](../../Shared/Views/ChatList/ChatListView.swift#L168) | 163 | Main view body |
+| [`filteredChats()`](../../Shared/Views/ChatList/ChatListView.swift#L480) | 472 | Applies active filter and search to chat list |
+| [`searchString()`](../../Shared/Views/ChatList/ChatListView.swift#L523) | 514 | Normalizes search text for comparison |
+| [`unreadBadge()`](../../Shared/Views/ChatList/ChatListView.swift#L454) | 448 | Renders unread count circle badge |
+| [`stopAudioPlayer()`](../../Shared/Views/ChatList/ChatListView.swift#L474) | 467 | Stops any playing voice message |
+
+---
+
+## 3. [`ChatPreviewView`](../../Shared/Views/ChatList/ChatPreviewView.swift#L13) {#3-chatpreviewview}
+
+**File**: `Shared/Views/ChatList/ChatPreviewView.swift`
+
+Renders a single row in the chat list. Shows:
+
+| Element | Source | Description |
+|---------|--------|-------------|
+| Avatar | `chatInfo.image` | Profile image or default icon |
+| Chat name | `chatInfo.displayName` | Contact name, group name, or connection label |
+| Last message | `chat.chatItems.last` | Preview text of most recent message |
+| Timestamp | `chat.chatItems.last?.timestampText` | Relative time of last message |
+| Unread badge | `chat.chatStats.unreadCount` | Circular badge with unread count |
+| Mute icon | `chatInfo.chatSettings?.enableNtfs` | Bell-slash icon if notifications muted |
+| Pin icon | -- | Pin indicator for pinned chats |
+| Incognito icon | Contact.contactConnIncognito | Incognito mode indicator |
+| Delivery status | Last sent item's `meta.itemStatus` | Check marks for delivery confirmation |
+
+### Preview Text Rendering
+- Text messages: first line of message content
+- Images: camera icon + caption (if any)
+- Files: paperclip icon + filename
+- Voice: microphone icon + duration
+- Calls: phone icon + call status
+- Group events: system event description
+- Encrypted/deleted: placeholder text
+
+---
+
+## 4. [`ChatListNavLink`](../../Shared/Views/ChatList/ChatListNavLink.swift#L44) {#4-chatlistnavlink}
+
+**File**: `Shared/Views/ChatList/ChatListNavLink.swift`
+
+Wraps `ChatPreviewView` in a navigation link with tap and swipe behavior:
+
+### Tap Behavior
+- Direct chat: navigates to `ChatView` via `ItemsModel.loadOpenChat(chatId)` -- [`contactNavLink()`](../../Shared/Views/ChatList/ChatListNavLink.swift#L95) L93
+- Group chat: navigates to `ChatView` -- [`groupNavLink()`](../../Shared/Views/ChatList/ChatListNavLink.swift#L217) L214
+- Contact request: shows `ContactRequestView` with accept/reject -- [`contactRequestNavLink()`](../../Shared/Views/ChatList/ChatListNavLink.swift#L495) L486
+- Contact connection: shows `ContactConnectionInfo` -- [`contactConnectionNavLink()`](../../Shared/Views/ChatList/ChatListNavLink.swift#L530) L520
+- Notes folder: navigates to `ChatView` -- [`noteFolderNavLink()`](../../Shared/Views/ChatList/ChatListNavLink.swift#L302) L298
+
+### Navigation
+- Uses `NavigationLink` (iOS 15) or programmatic navigation (iOS 16+)
+- Sets `ChatModel.chatId` to trigger navigation
+- `ItemsModel.loadOpenChat()` loads messages with a 250ms navigation delay for smooth animation
+
+---
+
+## 5. Filtering & Tags
+
+### Filter Tabs ([`TagListView`](../../Shared/Views/ChatList/TagListView.swift#L20))
+
+**File**: `Shared/Views/ChatList/TagListView.swift`
+
+Horizontal scrolling tab bar below the navigation bar. Tabs:
+
+| Tab | Filter | Shows |
+|-----|--------|-------|
+| All | `nil` | All conversations |
+| Unread | `.unread` | Conversations with unread messages |
+| Favorites | `.presetTag(.favorites)` | Favorited conversations |
+| Groups | `.presetTag(.groups)` | Group conversations |
+| Contacts | `.presetTag(.contacts)` | Direct conversations |
+| Business | `.presetTag(.business)` | Business conversations |
+| Group Reports | `.presetTag(.groupReports)` | Groups with pending reports |
+| User tags | `.userTag(ChatTag)` | User-defined custom tags |
+
+Filter matching is handled by [`presetTagMatchesChat()`](../../Shared/Views/ChatList/ChatListView.swift#L910) (L910) and the in-view [`TagsView`](../../Shared/Views/ChatList/ChatListView.swift#L705) struct (L705).
+
+### ChatTagsModel State
+
+Filtering state is managed by [`ChatTagsModel`](../../Shared/Model/ChatModel.swift#L189) (`ChatModel.swift` L183):
+
+```swift
+class ChatTagsModel: ObservableObject {
+ @Published var userTags: [ChatTag] = []
+ @Published var activeFilter: ActiveFilter? = nil
+ @Published var presetTags: [PresetTag: Int] = [:] // count per preset tag
+ @Published var unreadTags: [Int64: Int] = [:] // unread count per user tag
+}
+```
+
+- `presetTags` counts are updated whenever `chats` changes via [`updateChatTags()`](../../Shared/Model/ChatModel.swift#L197) (L197)
+- Tags with zero matching chats are auto-hidden
+- Active filter is auto-cleared when its tag has no matching chats
+
+### Supporting Types
+
+| Type | File | Line | Description |
+|------|------|------|-------------|
+| [`PresetTag`](../../Shared/Views/ChatList/ChatListView.swift#L36) | ChatListView.swift | 34 | Enum of built-in filter categories |
+| [`ActiveFilter`](../../Shared/Views/ChatList/ChatListView.swift#L52) | ChatListView.swift | 49 | Enum wrapping preset, user-tag, or unread filter |
+| [`setActiveFilter()`](../../Shared/Views/ChatList/ChatListView.swift#L889) | ChatListView.swift | 878 | Applies a filter and persists selection |
+
+### Tag Management Commands
+- `apiCreateChatTag(tag: ChatTagData)` -- create tag
+- `apiSetChatTags(type:, id:, tagIds:)` -- assign tags to a chat
+- `apiDeleteChatTag(tagId:)` -- delete tag
+- `apiUpdateChatTag(tagId:, tagData:)` -- rename tag
+- `apiReorderChatTags(tagIds:)` -- reorder tags
+
+---
+
+## 6. Search
+
+Search is available via pull-down gesture or search button in the navigation bar.
+
+**Search bar UI:** [`ChatListSearchBar`](../../Shared/Views/ChatList/ChatListView.swift#L587) (ChatListView.swift L578)
+
+### Filtering Logic
+- Filters `ChatModel.chats` by matching search text against:
+ - `chatInfo.displayName` (contact/group name)
+ - `chatInfo.localAlias` (local alias)
+ - `chatInfo.fullName` (full name)
+- For deeper message content search, uses `apiGetChat(chatId:, search:)` parameter
+- Core logic in [`filteredChats()`](../../Shared/Views/ChatList/ChatListView.swift#L480) (L480) and [`searchString()`](../../Shared/Views/ChatList/ChatListView.swift#L523) (L523)
+
+### Search Results
+- Matching chats are displayed in the same list format
+- Results update as the user types (debounced)
+- Clearing search restores the full filtered list
+
+---
+
+## 7. Swipe Actions
+
+`ChatListNavLink` provides swipe actions on each row:
+
+### Leading Swipe (left-to-right)
+
+| Action | Icon | Handler | Line | API | Condition |
+|--------|------|---------|------|-----|-----------|
+| Pin / Unpin | pin | [`toggleFavoriteButton()`](../../Shared/Views/ChatList/ChatListNavLink.swift#L353) | 347 | `apiSetChatSettings` (favorite) | Always |
+| Read / Unread | envelope | [`markReadButton()`](../../Shared/Views/ChatList/ChatListNavLink.swift#L333) | 328 | `apiChatRead` / `apiChatUnread` | Always |
+
+### Trailing Swipe (right-to-left)
+
+| Action | Icon | Handler | Line | API | Condition |
+|--------|------|---------|------|-----|-----------|
+| Mute / Unmute | bell.slash | [`toggleNtfsButton()`](../../Shared/Views/ChatList/ChatListNavLink.swift#L372) | 365 | `apiSetChatSettings` (enableNtfs) | Always |
+| Clear | trash | [`clearChatButton()`](../../Shared/Views/ChatList/ChatListNavLink.swift#L393) | 385 | `apiClearChat` | Has messages |
+| Delete | trash.fill | -- | -- | `apiDeleteChat` | Not active chat |
+| Tag | tag | -- | -- | `apiSetChatTags` | Always |
+
+---
+
+## 8. [`UserPicker`](../../Shared/Views/ChatList/UserPicker.swift#L10) {#8-userpicker}
+
+**File**: `Shared/Views/ChatList/UserPicker.swift`
+
+Triggered by tapping the user avatar in the navigation bar. Presented as a sheet with:
+
+| Section | Contents |
+|---------|----------|
+| User list | All non-hidden users with unread counts |
+| Active user | Highlighted with checkmark |
+| Actions | Settings, Your SimpleX address, User profiles |
+
+### User Switching
+- Tapping a different user calls `apiSetActiveUser(userId:)`
+- Triggers `apiGetChats` for the new user
+- `ChatModel.currentUser` updates, causing full UI refresh
+- Hidden users are not shown (require password entry via settings)
+
+---
+
+## 9. Floating Action Button
+
+The FAB (floating action button) in the bottom-right corner opens the new chat flow:
+
+- Tap: opens `NewChatView` sheet for creating a new contact connection or group
+- Shows options: Create link, Scan QR code, Paste link, Create group
+
+---
+
+## Source Files
+
+| File | Path | Key struct | Line |
+|------|------|------------|------|
+| Chat list view | [`ChatListView.swift`](../../Shared/Views/ChatList/ChatListView.swift) | `ChatListView` | [138](../../Shared/Views/ChatList/ChatListView.swift#L142) |
+| Chat preview row | [`ChatPreviewView.swift`](../../Shared/Views/ChatList/ChatPreviewView.swift) | `ChatPreviewView` | [12](../../Shared/Views/ChatList/ChatPreviewView.swift#L13) |
+| Navigation link wrapper | [`ChatListNavLink.swift`](../../Shared/Views/ChatList/ChatListNavLink.swift) | `ChatListNavLink` | [43](../../Shared/Views/ChatList/ChatListNavLink.swift#L44) |
+| Tag filter tabs | [`TagListView.swift`](../../Shared/Views/ChatList/TagListView.swift) | `TagListView` | [19](../../Shared/Views/ChatList/TagListView.swift#L20) |
+| User picker sheet | [`UserPicker.swift`](../../Shared/Views/ChatList/UserPicker.swift) | `UserPicker` | [9](../../Shared/Views/ChatList/UserPicker.swift#L10) |
+| Getting started help | [`ChatHelp.swift`](../../Shared/Views/ChatList/ChatHelp.swift) | | |
+| Contact request view | [`ContactRequestView.swift`](../../Shared/Views/ChatList/ContactRequestView.swift) | | |
+| Contact connection info | [`ContactConnectionInfo.swift`](../../Shared/Views/ChatList/ContactConnectionInfo.swift) | | |
+| Contact connection view | [`ContactConnectionView.swift`](../../Shared/Views/ChatList/ContactConnectionView.swift) | | |
+| Server summary | [`ServersSummaryView.swift`](../../Shared/Views/ChatList/ServersSummaryView.swift) | | |
+| One-hand UI card | [`OneHandUICard.swift`](../../Shared/Views/ChatList/OneHandUICard.swift) | | |
diff --git a/apps/ios/spec/client/chat-view.md b/apps/ios/spec/client/chat-view.md
new file mode 100644
index 0000000000..b913287746
--- /dev/null
+++ b/apps/ios/spec/client/chat-view.md
@@ -0,0 +1,331 @@
+# SimpleX Chat iOS -- Chat View Module
+
+> Technical specification for the message rendering, chat item types, and context menu actions in the conversation view.
+>
+> Related specs: [Compose Module](compose.md) | [State Management](../state.md) | [API Reference](../api.md) | [README](../README.md)
+> Related product: [Chat View](../../product/views/chat.md)
+
+**Source:** [`ChatView.swift`](../../Shared/Views/Chat/ChatView.swift) | [`ChatInfoView.swift`](../../Shared/Views/Chat/ChatInfoView.swift) | [`GroupChatInfoView.swift`](../../Shared/Views/Chat/Group/GroupChatInfoView.swift)
+
+---
+
+## Table of Contents
+
+1. [Overview](#1-overview)
+2. [ChatView](#2-chatview)
+3. [ChatItemView -- Message Routing](#3-chatitemview)
+4. [Message Renderers](#4-message-renderers)
+5. [Media Views](#5-media-views)
+6. [Metadata & Info](#6-metadata--info)
+7. [Context Menu Actions](#7-context-menu-actions)
+8. [Selection Mode](#8-selection-mode)
+
+---
+
+## 1. Overview
+
+The chat view module renders individual conversations. It consists of:
+
+- **ChatView** -- The main conversation screen with message list, compose bar, and navigation
+- **ChatItemView** -- Router that dispatches each chat item to the appropriate renderer
+- **Specialized renderers** -- FramedItemView (standard messages), EmojiItemView (emoji-only), CICallItemView (calls), event views, etc.
+- **Media views** -- CIImageView, CIVideoView, CIVoiceView, CIFileView for attachments
+
+```
+ChatView
+├── Message List (ScrollView / LazyVStack)
+│ ├── ChatItemView (per message)
+│ │ ├── FramedItemView (text/media bubbles)
+│ │ │ ├── MsgContentView (text with markdown)
+│ │ │ ├── CIImageView / CIVideoView / CIVoiceView
+│ │ │ └── CIMetaView (timestamp, status)
+│ │ ├── EmojiItemView (emoji-only messages)
+│ │ ├── CICallItemView (call events)
+│ │ ├── CIEventView (system events)
+│ │ ├── CIGroupInvitationView (group invitations)
+│ │ ├── DeletedItemView / MarkedDeletedItemView
+│ │ └── CIInvalidJSONView (decode errors)
+│ └── ... (more items)
+├── ComposeView (message input)
+└── Navigation bar (contact/group info)
+```
+
+---
+
+## [2. ChatView](../../Shared/Views/Chat/ChatView.swift#L18-L3135)
+
+**File**: [`Shared/Views/Chat/ChatView.swift`](../../Shared/Views/Chat/ChatView.swift)
+
+The main conversation view. Key responsibilities:
+
+### State
+- Uses `ItemsModel.shared.reversedChatItems` for the primary message list
+- `ChatModel.shared.chatId` identifies the active conversation
+- Manages compose state, scroll position, keyboard visibility
+- Tracks selection mode for multi-message actions
+
+### Message List
+- Renders messages in a `ScrollViewReader` with `LazyVStack`
+- Items are in reverse chronological order (newest at bottom)
+- Supports infinite scroll: preloads older messages when scrolling up via `ItemsModel.preloadState`
+- Handles pagination splits (`chatState.splits`) for non-contiguous loaded ranges
+
+### Navigation Bar
+- Title: contact name / group name with connection status indicator
+- Trailing button: navigates to [`ChatInfoView`](../../Shared/Views/Chat/ChatInfoView.swift#L93) (direct) or [`GroupChatInfoView`](../../Shared/Views/Chat/Group/GroupChatInfoView.swift#L16) (group)
+- Search button: toggles in-chat message search
+
+### Scroll Behavior
+- Auto-scrolls to bottom on new sent/received messages (if already near bottom)
+- "Scroll to bottom" floating button when scrolled up
+- `openAroundItemId` support: scrolls to a specific message (e.g., from search or notification)
+
+### Key Functions
+
+| Function | Line | Description |
+|----------|------|-------------|
+| [`body`](../../Shared/Views/Chat/ChatView.swift#L76) | L74 | Main view body |
+| [`initChatView()`](../../Shared/Views/Chat/ChatView.swift#L675) | L672 | Initializes chat view state on appear |
+| [`chatItemsList()`](../../Shared/Views/Chat/ChatView.swift#L821) | L814 | Builds the scrollable message list |
+| [`scrollToItem(_:)`](../../Shared/Views/Chat/ChatView.swift#L735) | L731 | Scrolls to a specific message by ID |
+| [`searchToolbar()`](../../Shared/Views/Chat/ChatView.swift#L769) | L764 | In-chat search toolbar UI |
+| [`searchTextChanged(_:)`](../../Shared/Views/Chat/ChatView.swift#L1095) | L1087 | Handles search query changes |
+| [`loadChatItems(_:_:)`](../../Shared/Views/Chat/ChatView.swift#L1531) | L1519 | Loads chat items with pagination |
+| [`filtered(_:)`](../../Shared/Views/Chat/ChatView.swift#L807) | L801 | Filters items by content type |
+| [`callButton(_:_:imageName:)`](../../Shared/Views/Chat/ChatView.swift#L1273) | L1264 | Audio/video call toolbar button |
+| [`searchButton()`](../../Shared/Views/Chat/ChatView.swift#L1293) | L1284 | Search toggle toolbar button |
+| [`addMembersButton()`](../../Shared/Views/Chat/ChatView.swift#L1361) | L1352 | Group add-members toolbar button |
+| [`forwardSelectedMessages()`](../../Shared/Views/Chat/ChatView.swift#L1420) | L1409 | Forwards batch-selected messages |
+| [`deletedSelectedMessages()`](../../Shared/Views/Chat/ChatView.swift#L1411) | L1401 | Deletes batch-selected messages |
+| [`onChatItemsUpdated()`](../../Shared/Views/Chat/ChatView.swift#L1572) | L1559 | Reacts to chat items model changes |
+| [`contentFilterMenu(withLabel:)`](../../Shared/Views/Chat/ChatView.swift#L1301) | L1292 | Content filter dropdown menu |
+
+### Supporting Types
+
+| Type | Line | Description |
+|------|------|-------------|
+| [`ChatItemWithMenu`](../../Shared/Views/Chat/ChatView.swift#L1600) | L1586 | Wraps each chat item with context menu |
+| [`FloatingButtonModel`](../../Shared/Views/Chat/ChatView.swift#L2712) | L2697 | Manages scroll-to-bottom button state |
+| [`ReactionContextMenu`](../../Shared/Views/Chat/ChatView.swift#L2899) | L2882 | Reaction picker context menu |
+| [`ToggleNtfsButton`](../../Shared/Views/Chat/ChatView.swift#L2997) | L2980 | Mute/unmute notifications button |
+| [`ContentFilter`](../../Shared/Views/Chat/ChatView.swift#L3049) | L3031 | Enum for message content filter types |
+| [`deleteMessages()`](../../Shared/Views/Chat/ChatView.swift#L2795) | L2779 | Deletes messages with confirmation |
+| [`archiveReports()`](../../Shared/Views/Chat/ChatView.swift#L2842) | L2826 | Archives report messages |
+
+---
+
+## [3. ChatItemView](../../Shared/Views/Chat/ChatItemView.swift#L42)
+
+**File**: [`Shared/Views/Chat/ChatItemView.swift`](../../Shared/Views/Chat/ChatItemView.swift)
+
+Routes each `ChatItem` to the appropriate renderer based on its `CIContent` type:
+
+### Content Types (CIContent enum)
+
+| Content Type | Renderer | Line | Description |
+|-------------|----------|------|-------------|
+| `sndMsgContent` / `rcvMsgContent` | [`FramedItemView`](../../Shared/Views/Chat/ChatItem/FramedItemView.swift#L14) | L13 | Standard sent/received text+media message |
+| `sndDeleted` / `rcvDeleted` | [`DeletedItemView`](../../Shared/Views/Chat/ChatItem/DeletedItemView.swift#L14) | L13 | Locally deleted message placeholder |
+| `sndCall` / `rcvCall` | [`CICallItemView`](../../Shared/Views/Chat/ChatItem/CICallItemView.swift#L13) | L13 | Call event (missed, ended, duration) |
+| `rcvIntegrityError` | [`IntegrityErrorItemView`](../../Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift#L14) | L13 | Message integrity error |
+| `rcvDecryptionError` | [`CIRcvDecryptionError`](../../Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift#L16) | L15 | Decryption failure |
+| `sndGroupInvitation` / `rcvGroupInvitation` | [`CIGroupInvitationView`](../../Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift#L14) | L13 | Group invite |
+| `sndGroupEvent` / `rcvGroupEvent` | [`CIEventView`](../../Shared/Views/Chat/ChatItem/CIEventView.swift#L14) | L13 | Group system event |
+| `rcvConnEvent` / `sndConnEvent` | [`CIEventView`](../../Shared/Views/Chat/ChatItem/CIEventView.swift#L14) | L13 | Connection event |
+| `rcvChatFeature` / `sndChatFeature` | [`CIChatFeatureView`](../../Shared/Views/Chat/ChatItem/CIChatFeatureView.swift#L14) | L13 | Feature toggle event |
+| `rcvChatPreference` / `sndChatPreference` | [`CIFeaturePreferenceView`](../../Shared/Views/Chat/ChatItem/CIFeaturePreferenceView.swift#L14) | L13 | Preference change |
+| `invalidJSON` | [`CIInvalidJSONView`](../../Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift#L14) | L13 | Failed to decode |
+
+### Bubble Direction
+- Sent messages: aligned right, sender-colored bubble
+- Received messages: aligned left, receiver-colored bubble
+- Events/system messages: centered, no bubble
+
+### Appearance Dependencies
+Each [`ChatItemWithMenu`](../../Shared/Views/Chat/ChatView.swift#L1600) may depend on the previous and next items for visual decisions:
+- Whether to show the sender name (group messages, different sender than previous)
+- Whether to show the tail on the bubble (last consecutive message from same sender)
+- Date separator between messages on different days
+
+`ChatItemDummyModel.shared.sendUpdate()` forces a re-render of all items when global appearance changes.
+
+---
+
+## 4. Message Renderers
+
+### [FramedItemView](../../Shared/Views/Chat/ChatItem/FramedItemView.swift#L14)
+
+**File**: [`Shared/Views/Chat/ChatItem/FramedItemView.swift`](../../Shared/Views/Chat/ChatItem/FramedItemView.swift)
+
+The standard message bubble. Renders:
+- Quote/reply preview (if replying to another message)
+- Forwarded indicator
+- Sender name (in groups)
+- Message content (`MsgContentView` with markdown)
+- Attached media (image, video, voice, file, link preview)
+- Reaction summary bar
+- Metadata line (`CIMetaView`)
+
+### [EmojiItemView](../../Shared/Views/Chat/ChatItem/EmojiItemView.swift#L14)
+
+**File**: [`Shared/Views/Chat/ChatItem/EmojiItemView.swift`](../../Shared/Views/Chat/ChatItem/EmojiItemView.swift)
+
+Renders emoji-only messages (messages containing only emoji characters) in a larger font without a bubble background.
+
+### [MsgContentView](../../Shared/Views/Chat/ChatItem/MsgContentView.swift#L28)
+
+**File**: [`Shared/Views/Chat/ChatItem/MsgContentView.swift`](../../Shared/Views/Chat/ChatItem/MsgContentView.swift)
+
+Renders message text with SimpleX markdown formatting (bold, italic, code, links, mentions).
+
+### DeletedItemView / MarkedDeletedItemView
+
+**Files**: [`Shared/Views/Chat/ChatItem/DeletedItemView.swift`](../../Shared/Views/Chat/ChatItem/DeletedItemView.swift) | [`Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift`](../../Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift)
+
+- [`DeletedItemView`](../../Shared/Views/Chat/ChatItem/DeletedItemView.swift#L14): Placeholder for locally deleted messages
+- [`MarkedDeletedItemView`](../../Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift#L14): Shows "message deleted" with optional moderation info (who deleted, when)
+
+### [CIEventView](../../Shared/Views/Chat/ChatItem/CIEventView.swift#L14)
+
+**File**: [`Shared/Views/Chat/ChatItem/CIEventView.swift`](../../Shared/Views/Chat/ChatItem/CIEventView.swift)
+
+Centered system event text for group events (member joined, left, role changed) and connection events.
+
+### [CIGroupInvitationView](../../Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift#L14)
+
+**File**: [`Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift`](../../Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift)
+
+Renders group invitation with accept/reject buttons.
+
+---
+
+## 5. Media Views
+
+### [CIImageView](../../Shared/Views/Chat/ChatItem/CIImageView.swift#L14)
+
+**File**: [`Shared/Views/Chat/ChatItem/CIImageView.swift`](../../Shared/Views/Chat/ChatItem/CIImageView.swift)
+
+Renders inline images. Tapping opens `FullScreenMediaView` for zooming/panning. Images are compressed to `MAX_IMAGE_SIZE` (255KB) before sending.
+
+### [CIVideoView](../../Shared/Views/Chat/ChatItem/CIVideoView.swift#L16)
+
+**File**: [`Shared/Views/Chat/ChatItem/CIVideoView.swift`](../../Shared/Views/Chat/ChatItem/CIVideoView.swift)
+
+Renders video thumbnails with play button. Tapping opens video player. Videos above auto-receive threshold require manual download.
+
+### CIVoiceView / FramedCIVoiceView
+
+**Files**: [`Shared/Views/Chat/ChatItem/CIVoiceView.swift`](../../Shared/Views/Chat/ChatItem/CIVoiceView.swift) | [`Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift`](../../Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift)
+
+Renders voice messages with waveform visualization, play/pause control, and duration. [`FramedCIVoiceView`](../../Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift#L16) is the version inside a message bubble with additional context.
+
+### [CIFileView](../../Shared/Views/Chat/ChatItem/CIFileView.swift#L14)
+
+**File**: [`Shared/Views/Chat/ChatItem/CIFileView.swift`](../../Shared/Views/Chat/ChatItem/CIFileView.swift)
+
+Renders file attachments with filename, size, and download/open actions. Shows transfer progress during upload/download.
+
+### [CILinkView](../../Shared/Views/Chat/ChatItem/CILinkView.swift#L14)
+
+**File**: [`Shared/Views/Chat/ChatItem/CILinkView.swift`](../../Shared/Views/Chat/ChatItem/CILinkView.swift)
+
+Renders link preview cards with OpenGraph metadata (title, description, image).
+
+### [AnimatedImageView](../../Shared/Views/Chat/ChatItem/AnimatedImageView.swift#L11)
+
+**File**: [`Shared/Views/Chat/ChatItem/AnimatedImageView.swift`](../../Shared/Views/Chat/ChatItem/AnimatedImageView.swift)
+
+Renders animated GIF images.
+
+### [FullScreenMediaView](../../Shared/Views/Chat/ChatItem/FullScreenMediaView.swift#L16)
+
+**File**: [`Shared/Views/Chat/ChatItem/FullScreenMediaView.swift`](../../Shared/Views/Chat/ChatItem/FullScreenMediaView.swift)
+
+Full-screen media viewer with zoom, pan, and share actions. Supports images and videos.
+
+---
+
+## 6. Metadata & Info
+
+### [CIMetaView](../../Shared/Views/Chat/ChatItem/CIMetaView.swift#L14)
+
+**File**: [`Shared/Views/Chat/ChatItem/CIMetaView.swift`](../../Shared/Views/Chat/ChatItem/CIMetaView.swift)
+
+Displays message metadata inline at the bottom of the bubble:
+- Timestamp (sent time)
+- Delivery status icon (sending, sent, delivered, read, error)
+- Edit indicator (pencil icon if message was edited)
+- Disappearing message timer (if timed message)
+
+### [ChatItemInfoView](../../Shared/Views/Chat/ChatItemInfoView.swift#L13)
+
+**File**: [`Shared/Views/Chat/ChatItemInfoView.swift`](../../Shared/Views/Chat/ChatItemInfoView.swift)
+
+Detailed message information sheet (accessed via long-press menu "Info"):
+- Full delivery history (per-member delivery status in groups)
+- Edit history (all previous versions of edited messages)
+- Forward chain info
+- Message timestamps (created, updated, deleted)
+
+---
+
+## 7. Context Menu Actions
+
+Long-pressing a message shows a context menu with actions based on message type and ownership:
+
+| Action | Available For | API Command |
+|--------|--------------|-------------|
+| Reply | All messages | Sets compose state to `.replying` |
+| Forward | Sent/received content messages | `apiForwardChatItems` |
+| Copy | Text messages | Copies to clipboard |
+| Edit | Own sent messages (within edit window) | `apiUpdateChatItem` |
+| Delete for me | All messages | `apiDeleteChatItem(mode: .cidmInternal)` |
+| Delete for everyone | Own sent messages | `apiDeleteChatItem(mode: .cidmBroadcast)` |
+| Moderate | Group admin/owner for others' messages | `apiDeleteMemberChatItem` |
+| React | Content messages (if reactions enabled) | `apiChatItemReaction` |
+| Select | All messages | Enters multi-select mode |
+| Info | All messages | Opens [`ChatItemInfoView`](../../Shared/Views/Chat/ChatItemInfoView.swift#L13) |
+| Save | Media messages | Saves to photo library / files |
+| Share | Content messages | iOS share sheet |
+
+---
+
+## 8. Selection Mode
+
+Multi-selection mode allows batch operations on messages:
+
+- Enter via long-press "Select" action
+- Toggle individual messages with tap
+- Toolbar appears with batch actions: Delete, Forward
+- Exit via cancel button or completing batch action
+
+---
+
+## Source Files
+
+| File | Path | Line |
+|------|------|------|
+| Chat view | [`Shared/Views/Chat/ChatView.swift`](../../Shared/Views/Chat/ChatView.swift) | [L17](../../Shared/Views/Chat/ChatView.swift#L18) |
+| Item router | [`Shared/Views/Chat/ChatItemView.swift`](../../Shared/Views/Chat/ChatItemView.swift) | [L41](../../Shared/Views/Chat/ChatItemView.swift#L42) |
+| Framed bubble | [`Shared/Views/Chat/ChatItem/FramedItemView.swift`](../../Shared/Views/Chat/ChatItem/FramedItemView.swift) | [L13](../../Shared/Views/Chat/ChatItem/FramedItemView.swift#L14) |
+| Emoji message | [`Shared/Views/Chat/ChatItem/EmojiItemView.swift`](../../Shared/Views/Chat/ChatItem/EmojiItemView.swift) | [L13](../../Shared/Views/Chat/ChatItem/EmojiItemView.swift#L14) |
+| Image view | [`Shared/Views/Chat/ChatItem/CIImageView.swift`](../../Shared/Views/Chat/ChatItem/CIImageView.swift) | [L13](../../Shared/Views/Chat/ChatItem/CIImageView.swift#L14) |
+| Video view | [`Shared/Views/Chat/ChatItem/CIVideoView.swift`](../../Shared/Views/Chat/ChatItem/CIVideoView.swift) | [L15](../../Shared/Views/Chat/ChatItem/CIVideoView.swift#L16) |
+| Voice view | [`Shared/Views/Chat/ChatItem/CIVoiceView.swift`](../../Shared/Views/Chat/ChatItem/CIVoiceView.swift) | [L13](../../Shared/Views/Chat/ChatItem/CIVoiceView.swift#L14) |
+| File view | [`Shared/Views/Chat/ChatItem/CIFileView.swift`](../../Shared/Views/Chat/ChatItem/CIFileView.swift) | [L13](../../Shared/Views/Chat/ChatItem/CIFileView.swift#L14) |
+| Link preview | [`Shared/Views/Chat/ChatItem/CILinkView.swift`](../../Shared/Views/Chat/ChatItem/CILinkView.swift) | [L13](../../Shared/Views/Chat/ChatItem/CILinkView.swift#L14) |
+| Call event | [`Shared/Views/Chat/ChatItem/CICallItemView.swift`](../../Shared/Views/Chat/ChatItem/CICallItemView.swift) | [L13](../../Shared/Views/Chat/ChatItem/CICallItemView.swift#L13) |
+| Metadata | [`Shared/Views/Chat/ChatItem/CIMetaView.swift`](../../Shared/Views/Chat/ChatItem/CIMetaView.swift) | [L13](../../Shared/Views/Chat/ChatItem/CIMetaView.swift#L14) |
+| Message info | [`Shared/Views/Chat/ChatItemInfoView.swift`](../../Shared/Views/Chat/ChatItemInfoView.swift) | [L12](../../Shared/Views/Chat/ChatItemInfoView.swift#L13) |
+| System event | [`Shared/Views/Chat/ChatItem/CIEventView.swift`](../../Shared/Views/Chat/ChatItem/CIEventView.swift) | [L13](../../Shared/Views/Chat/ChatItem/CIEventView.swift#L14) |
+| Deleted placeholder | [`Shared/Views/Chat/ChatItem/DeletedItemView.swift`](../../Shared/Views/Chat/ChatItem/DeletedItemView.swift) | [L13](../../Shared/Views/Chat/ChatItem/DeletedItemView.swift#L14) |
+| Moderated placeholder | [`Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift`](../../Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift) | [L13](../../Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift#L14) |
+| Text content | [`Shared/Views/Chat/ChatItem/MsgContentView.swift`](../../Shared/Views/Chat/ChatItem/MsgContentView.swift) | [L27](../../Shared/Views/Chat/ChatItem/MsgContentView.swift#L28) |
+| Group invitation | [`Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift`](../../Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift) | [L13](../../Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift#L14) |
+| Feature event | [`Shared/Views/Chat/ChatItem/CIChatFeatureView.swift`](../../Shared/Views/Chat/ChatItem/CIChatFeatureView.swift) | [L13](../../Shared/Views/Chat/ChatItem/CIChatFeatureView.swift#L14) |
+| Decryption error | [`Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift`](../../Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift) | [L15](../../Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift#L16) |
+| Integrity error | [`Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift`](../../Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift) | [L13](../../Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift#L14) |
+| Full-screen media | [`Shared/Views/Chat/ChatItem/FullScreenMediaView.swift`](../../Shared/Views/Chat/ChatItem/FullScreenMediaView.swift) | [L15](../../Shared/Views/Chat/ChatItem/FullScreenMediaView.swift#L16) |
+| Animated image | [`Shared/Views/Chat/ChatItem/AnimatedImageView.swift`](../../Shared/Views/Chat/ChatItem/AnimatedImageView.swift) | [L10](../../Shared/Views/Chat/ChatItem/AnimatedImageView.swift#L11) |
+| Framed voice | [`Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift`](../../Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift) | [L15](../../Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift#L16) |
+| Member contact | [`Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift`](../../Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift) | [L13](../../Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift#L14) |
diff --git a/apps/ios/spec/client/compose.md b/apps/ios/spec/client/compose.md
new file mode 100644
index 0000000000..03116ddf6b
--- /dev/null
+++ b/apps/ios/spec/client/compose.md
@@ -0,0 +1,355 @@
+# SimpleX Chat iOS -- Message Composition Module
+
+> Technical specification for the compose bar, attachment types, reply/edit/forward modes, voice recording, and mentions.
+>
+> Related specs: [Chat View](chat-view.md) | [File Transfer](../services/files.md) | [API Reference](../api.md) | [README](../README.md)
+> Related product: [Chat View](../../product/views/chat.md)
+
+**Source:** [`ComposeView.swift`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift)
+
+---
+
+## Table of Contents
+
+1. [Overview](#1-overview)
+2. [ComposeView](#2-composeview)
+3. [ComposeState Machine](#3-composestate-machine)
+4. [Attachment Types](#4-attachment-types)
+5. [Reply Mode](#5-reply-mode)
+6. [Edit Mode](#6-edit-mode)
+7. [Forward Mode](#7-forward-mode)
+8. [Live Messages](#8-live-messages)
+9. [Voice Recording](#9-voice-recording)
+10. [Link Previews](#10-link-previews)
+11. [Mentions](#11-mentions)
+
+---
+
+## 1. Overview
+
+The compose module handles all message creation, editing, and forwarding. It sits at the bottom of `ChatView` and adapts its UI based on the current compose state.
+
+```
+ComposeView
+├── Context banner (reply quote / edit indicator / forward indicator)
+├── Attachment preview (image / video / file / voice waveform)
+├── Text input (NativeTextEditor with markdown support)
+├── Action buttons
+│ ├── Attachment menu (camera, photo library, file picker)
+│ ├── Voice record button (hold or toggle)
+│ └── Send button (or live message indicator)
+└── Link preview (auto-generated when URL detected)
+```
+
+---
+
+## 2. [ComposeView](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L329) (`struct ComposeView: View`)
+
+**File**: `Shared/Views/Chat/ComposeMessage/ComposeView.swift`
+
+### Layout
+- Fixed at the bottom of ChatView
+- Expands vertically as text input grows (up to a maximum height)
+- Context banner appears above the text field when in reply/edit/forward mode
+- Attachment preview appears between context banner and text field
+
+### Key Properties
+- Reads `ChatModel.shared.draft` / `draftChatId` for persisted drafts
+- Manages its own internal compose state
+- Coordinates with `ChatView` for scroll-to-bottom behavior on send
+
+### Send Flow
+1. User taps send button
+2. ComposeView constructs `[ComposedMessage]` from current state
+3. Calls `apiSendMessages(type:, id:, scope:, live:, ttl:, composedMessages:)`
+4. On success: clears compose state, scrolls to bottom
+5. On failure: shows error alert, preserves compose state
+
+### Key Functions
+
+| Function | Line | Description |
+|----------|------|-------------|
+| [`body`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L369) | L360 | Main view body |
+| [`sendMessageView()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L693) | L683 | Builds the send-message UI |
+| [`sendMessage(ttl:)`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1106) | L1091 | Entry point: initiates send |
+| [`sendMessageAsync()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1115) | L1099 | Async send implementation |
+| [`clearState(live:)`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1467) | L1446 | Resets compose state after send |
+| [`addMediaContent()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L893) | L882 | Adds media attachment |
+| [`connectCheckLinkPreview()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L866) | L856 | Checks link preview before connect |
+| [`commandsButton()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L754) | L744 | Builds commands menu button |
+
+### Draft Persistence
+
+| Function | Line | Description |
+|----------|------|-------------|
+| [`saveCurrentDraft()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1481) | L1459 | Saves compose state to `ChatModel.draft` |
+| [`clearCurrentDraft()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1487) | L1464 | Clears persisted draft |
+
+- When navigating away from a chat, compose state is saved to `ChatModel.draft` / `ChatModel.draftChatId`
+- When returning to the same chat, draft is restored
+- Drafts are not persisted across app restarts
+
+---
+
+## 3. [ComposeState](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L45) Machine (`struct ComposeState`)
+
+The compose bar operates as a state machine with these primary states:
+
+```
+ ┌──────────┐
+ │ .empty │ ← initial / after send
+ └─────┬────┘
+ │ user types / attaches / quotes
+ v
+ ┌─────────────────────────────────────┐
+ │ │
+ ┌────▼────┐ ┌──────────────┐ ┌──────────▼───┐
+ │ .text │ │ .mediaPending │ │ .voiceRecording │
+ └─────────┘ └──────────────┘ └───────────────┘
+ │ │
+ │ long-press reply│ tap edit
+ v v
+ ┌──────────┐ ┌──────────┐ ┌───────────┐
+ │ .replying │ │ .editing │ │ .forwarding│
+ └──────────┘ └──────────┘ └───────────┘
+```
+
+### Supporting Types
+
+| Type | Line | Description |
+|------|------|-------------|
+| [`enum ComposePreview`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L11) | L10 | Preview variants (image, voice, file, etc.) |
+| [`enum ComposeContextItem`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L20) | L18 | Context item for reply/quote |
+| [`enum VoiceMessageRecordingState`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L29) | L26 | Recording state enum |
+| [`struct ComposeState`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L45) | L40 | Full compose state struct |
+| [`copy()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L98) | L93 | Copy compose state with overrides |
+| [`mentionMemberName()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L118) | L113 | Format mention display name |
+| [`chatItemPreview()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L266) | L260 | Build preview from chat item |
+| [`enum UploadContent`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L287) | L280 | Upload content variants |
+
+### States
+
+| State | Description | UI |
+|-------|-------------|-----|
+| `.empty` | No input, no attachments | Placeholder text, attachment button |
+| `.text` | Text entered, no attachments | Send button visible |
+| `.mediaPending` | Media/file selected, optionally with text | Preview visible, send button |
+| `.voiceRecording` | Voice recording in progress | Waveform animation, stop/send |
+| `.replying` | Replying to a specific message | Quote banner above input |
+| `.editing` | Editing a previously sent message | Edit banner, pre-filled text |
+| `.forwarding` | Forwarding selected messages | Forward banner, item previews |
+
+### Transitions
+
+| From | Trigger | To |
+|------|---------|-----|
+| `.empty` | User types text | `.text` |
+| `.empty` | User selects media | `.mediaPending` |
+| `.empty` | User holds voice button | `.voiceRecording` |
+| `.empty` | User long-presses message "Reply" | `.replying` |
+| `.empty` | User long-presses message "Edit" | `.editing` |
+| `.empty` | User selects "Forward" | `.forwarding` |
+| Any | User taps send | `.empty` |
+| Any | User taps cancel (X) | `.empty` |
+
+---
+
+## 4. Attachment Types
+
+### [ComposeImageView](../../Shared/Views/Chat/ComposeMessage/ComposeImageView.swift#L12)
+
+**File**: [`ComposeImageView.swift`](../../Shared/Views/Chat/ComposeMessage/ComposeImageView.swift) (struct at L12)
+
+Preview of selected image(s) before sending. Shows thumbnail with remove button. Images are compressed to `MAX_IMAGE_SIZE` (255KB) before sending.
+
+### [ComposeFileView](../../Shared/Views/Chat/ComposeMessage/ComposeFileView.swift#L11)
+
+**File**: [`ComposeFileView.swift`](../../Shared/Views/Chat/ComposeMessage/ComposeFileView.swift) (struct at L11)
+
+Preview of selected file or video. Shows filename, size, and remove button. Videos show a thumbnail frame.
+
+### [ComposeVoiceView](../../Shared/Views/Chat/ComposeMessage/ComposeVoiceView.swift#L26)
+
+**File**: [`ComposeVoiceView.swift`](../../Shared/Views/Chat/ComposeMessage/ComposeVoiceView.swift) (struct at L26)
+
+Voice message recording/playback preview. Shows waveform visualization, duration, and play/delete buttons.
+
+### Attachment Menu Options
+
+| Option | Picker | Max Size | Transfer Method |
+|--------|--------|----------|-----------------|
+| Camera photo | UIImagePickerController | Compressed to 255KB | Inline in SMP message |
+| Photo library | PHPickerViewController | Compressed to 255KB | Inline or XFTP |
+| Video | PHPickerViewController | Up to 1GB | XFTP |
+| File | UIDocumentPickerViewController | Up to 1GB | XFTP |
+
+---
+
+## 5. Reply Mode
+
+Activated via long-press context menu "Reply" on any message.
+
+### UI
+- Quote banner above text input showing original message preview
+- X button to cancel reply
+- Original message reference stored in compose state
+
+### API
+- Reply is sent as part of `ComposedMessage` with `quotedItemId` parameter
+- `apiSendMessages(composedMessages: [ComposedMessage(quotedItemId: originalItem.id, ...)])`
+
+---
+
+## 6. Edit Mode
+
+Activated via long-press context menu "Edit" on own sent messages (within the edit window).
+
+### UI
+- Edit banner above text input with pencil icon
+- Text field pre-filled with original message content
+- Send button changes to "Save" / checkmark
+
+### API
+- `apiUpdateChatItem(type:, id:, scope:, itemId:, updatedMessage:, live:)`
+- Response: `ChatResponse1.chatItemUpdated(user:, chatItem:)`
+
+### Constraints
+- Only own sent messages can be edited
+- Edit is available within a server-defined time window
+- Edited messages show a pencil indicator in `CIMetaView`
+- Edit history is visible in `ChatItemInfoView`
+
+---
+
+## 7. Forward Mode
+
+Activated via long-press context menu "Forward" or via multi-select toolbar.
+
+### Flow
+1. User selects "Forward" on message(s)
+2. `apiPlanForwardChatItems(fromChatType:, fromChatId:, fromScope:, itemIds:)` is called to plan
+3. Response: `ChatResponse1.forwardPlan(user:, chatItemIds:, forwardConfirmation:)`
+4. User selects destination chat
+5. `apiForwardChatItems(toChatType:, toChatId:, toScope:, fromChatType:, fromChatId:, fromScope:, itemIds:, ttl:)` executes the forward
+6. Forwarded messages appear with a forwarded indicator
+
+### ForwardConfirmation
+The plan response may include a `forwardConfirmation` requiring user confirmation (e.g., forwarding to a less secure chat).
+
+---
+
+## 8. [Live Messages](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L36) (`struct LiveMessage`)
+
+Optional feature where the recipient sees typing in real-time.
+
+### How It Works
+- User enables live message mode (lightning icon)
+- As user types, `apiSendMessages(live: true)` is called repeatedly
+- Each call sends the current text as an update to the same message
+- Recipient sees the message being composed in real-time
+- Final send marks the message as complete
+
+### Key Functions
+
+| Function | Line | Description |
+|----------|------|-------------|
+| [`sendLiveMessage()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L922) | L910 | Initiates a live message |
+| [`updateLiveMessage()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L940) | L927 | Sends incremental live update |
+| [`liveMessageToSend()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L959) | L945 | Determines text diff to send |
+| [`truncateToWords()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L964) | L950 | Truncates text at word boundary |
+
+### API
+- Initial: `apiSendMessages(live: true, composedMessages: [...])` -- creates live message
+- Updates: `apiUpdateChatItem(live: true)` -- updates content as user types
+- Final: `apiUpdateChatItem(live: false)` -- marks as complete
+
+---
+
+## 9. Voice Recording
+
+### Recording Flow
+1. User taps (or holds) the microphone button
+2. `AVAudioRecorder` starts recording in compressed format
+3. Waveform visualization shows real-time audio levels
+4. User taps stop (or releases hold) to finish recording
+5. Preview with playback shown in compose area
+6. User taps send to deliver
+
+### Voice Functions
+
+| Function | Line | Description |
+|----------|------|-------------|
+| [`startVoiceMessageRecording()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1382) | L1365 | Begins audio recording |
+| [`finishVoiceMessageRecording()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1423) | L1405 | Stops recording, shows preview |
+| [`allowVoiceMessagesToContact()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1434) | L1415 | Enables voice messages for contact |
+| [`updateComposeVMRFinished()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1441) | L1422 | Updates state after recording finishes |
+| [`cancelCurrentVoiceRecording()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1453) | L1434 | Cancels in-progress recording |
+| [`cancelVoiceMessageRecording()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1460) | L1440 | Cancels and cleans up recording file |
+
+### Constraints
+- Maximum duration: `MAX_VOICE_MESSAGE_LENGTH = 300` seconds (5 minutes)
+- Auto-receive threshold: `MAX_VOICE_SIZE_AUTO_RCV = 522,240` bytes (510KB)
+- Compressed audio format for small file sizes
+
+### Audio Management
+- [`AudioRecorder`](../../Shared/Model/AudioRecPlay.swift#L14) (`Shared/Model/AudioRecPlay.swift` L14) manages recording and playback
+- `ChatModel.stopPreviousRecPlay` coordinates exclusive audio playback (only one audio source plays at a time)
+
+---
+
+## 10. [Link Previews](../../Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift#L13) (`ComposeLinkView`)
+
+**File**: [`ComposeLinkView.swift`](../../Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift) (struct at L13)
+
+### Auto-Detection
+- As user types, URLs in the text are detected
+- When a URL is found, `ComposeLinkView` fetches OpenGraph metadata
+- Preview card shows title, description, and thumbnail image
+
+### Link Preview Functions
+
+| Function | Line | Description |
+|----------|------|-------------|
+| [`showLinkPreview()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1495) | L1471 | Triggers link preview loading |
+| [`getMessageLinks()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1515) | L1490 | Extracts URLs from formatted text |
+| [`isSimplexLink()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1526) | L1501 | Checks if URL is a SimpleX link |
+| [`cancelLinkPreview()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1530) | L1505 | Cancels pending preview |
+| [`loadLinkPreview()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1542) | L1516 | Fetches OpenGraph metadata |
+| [`resetLinkPreview()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1559) | L1533 | Resets preview state |
+
+### Behavior
+- Only the first URL in the message generates a preview
+- Preview can be dismissed by the user
+- Link preview data is included in the `ComposedMessage` sent to the core
+- Toggle in privacy settings to disable auto-preview generation
+
+---
+
+## 11. Mentions
+
+In group chats, typing `@` triggers member name autocomplete:
+
+### Flow
+1. User types `@` in the text field
+2. Autocomplete dropdown appears with matching group members
+3. User selects a member
+4. `@displayName` is inserted into the text
+5. Mention is rendered with special formatting in the sent message
+
+### Data
+- Group members loaded from `ChatModel.groupMembers`
+- Mention metadata included in `ComposedMessage`
+
+---
+
+## Source Files
+
+| File | Path | Struct/Class | Line |
+|------|------|--------------|------|
+| Compose view | [`ComposeView.swift`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift) | `ComposeView` | [L321](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L329) |
+| Send message UI | [`SendMessageView.swift`](../../Shared/Views/Chat/ComposeMessage/SendMessageView.swift) | `SendMessageView` | [L14](../../Shared/Views/Chat/ComposeMessage/SendMessageView.swift#L15) |
+| Image preview | [`ComposeImageView.swift`](../../Shared/Views/Chat/ComposeMessage/ComposeImageView.swift) | `ComposeImageView` | [L12](../../Shared/Views/Chat/ComposeMessage/ComposeImageView.swift#L12) |
+| File preview | [`ComposeFileView.swift`](../../Shared/Views/Chat/ComposeMessage/ComposeFileView.swift) | `ComposeFileView` | [L11](../../Shared/Views/Chat/ComposeMessage/ComposeFileView.swift#L11) |
+| Voice preview | [`ComposeVoiceView.swift`](../../Shared/Views/Chat/ComposeMessage/ComposeVoiceView.swift) | `ComposeVoiceView` | [L26](../../Shared/Views/Chat/ComposeMessage/ComposeVoiceView.swift#L26) |
+| Link preview | [`ComposeLinkView.swift`](../../Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift) | `ComposeLinkView` | [L13](../../Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift#L13) |
+| Audio recording | [`AudioRecPlay.swift`](../../Shared/Model/AudioRecPlay.swift) | `AudioRecorder` | [L14](../../Shared/Model/AudioRecPlay.swift#L14) |
diff --git a/apps/ios/spec/client/navigation.md b/apps/ios/spec/client/navigation.md
new file mode 100644
index 0000000000..e755115827
--- /dev/null
+++ b/apps/ios/spec/client/navigation.md
@@ -0,0 +1,312 @@
+# SimpleX Chat iOS -- Navigation Architecture
+
+> Technical specification for the navigation stack, deep linking, sheet presentation, and call overlay.
+>
+> Related specs: [Chat List](chat-list.md) | [Chat View](chat-view.md) | [State Management](../state.md) | [README](../README.md)
+> Related product: [Product Overview](../../product/README.md)
+
+**Source:** [`ContentView.swift`](../../Shared/ContentView.swift) | [`NewChatView.swift`](../../Shared/Views/NewChat/NewChatView.swift) | [`SettingsView.swift`](../../Shared/Views/UserSettings/SettingsView.swift) | [`OnboardingView.swift`](../../Shared/Views/Onboarding/OnboardingView.swift) | [`UserProfilesView.swift`](../../Shared/Views/UserSettings/UserProfilesView.swift)
+
+---
+
+## Table of Contents
+
+1. [Overview](#1-overview)
+2. [Root View -- ContentView](#2-root-view)
+3. [Navigation Stack](#3-navigation-stack)
+4. [Sheet Presentation](#4-sheet-presentation)
+5. [Deep Linking](#5-deep-linking)
+6. [Call Overlay](#6-call-overlay)
+7. [Authentication Gate](#7-authentication-gate)
+8. [Onboarding Flow](#8-onboarding-flow)
+
+---
+
+## 1. Overview
+
+The app's navigation follows a hierarchical model with a single navigation stack rooted in `ContentView`. Modal sheets and full-screen overlays augment the primary navigation path.
+
+```
+SimpleXApp
+└── ContentView (root)
+ ├── Authentication gate (LocalAuthView / SetAppPasscodeView)
+ ├── Onboarding flow (if first launch / migration)
+ ├── Main content
+ │ └── NavigationStack / NavigationView
+ │ ├── ChatListView (root of stack)
+ │ │ ├── ChatView (pushed)
+ │ │ │ ├── ChatInfoView / GroupChatInfoView (pushed)
+ │ │ │ └── ChatItemInfoView (pushed)
+ │ │ └── ContactConnectionInfo (pushed)
+ │ └── Settings views (pushed)
+ ├── Sheets (modal)
+ │ ├── UserPicker
+ │ ├── NewChatView
+ │ ├── WhatsNew / Notices
+ │ └── Settings sub-views
+ └── Overlays (always on top)
+ ├── Active call banner (when call active)
+ └── ActiveCallView (full-screen call)
+```
+
+---
+
+## 2. Root View -- [`ContentView`](../../Shared/ContentView.swift#L24)
+
+**File**: [`Shared/ContentView.swift`](../../Shared/ContentView.swift)
+
+`ContentView` is the root view injected by `SimpleXApp`. It manages:
+
+### [Environment](../../Shared/ContentView.swift#L25-L37)
+- `@EnvironmentObject var chatModel: ChatModel`
+- `@EnvironmentObject var theme: AppTheme`
+- `@Environment(\.scenePhase) var scenePhase`
+
+### [Key State](../../Shared/ContentView.swift#L35-L52)
+| Property | Type | Purpose |
+|----------|------|---------|
+| [`contentAccessAuthenticationExtended`](../../Shared/ContentView.swift#L35) | `Bool` | Passed at init to avoid re-render timing issues |
+| [`automaticAuthenticationAttempted`](../../Shared/ContentView.swift#L38) | `Bool` | Whether biometric auth was auto-attempted |
+| [`waitingForOrPassedAuth`](../../Shared/ContentView.swift#L51) | `Bool` | Whether auth gate should show |
+| [`chatListUserPickerSheet`](../../Shared/ContentView.swift#L52) | `UserPickerSheet?` | Active user picker sheet |
+
+### [View Selection Logic](../../Shared/ContentView.swift#L60-L80)
+
+```swift
+// Simplified decision tree in ContentView.body:
+if !prefPerformLA || accessAuthenticated {
+ contentView() // Main app content
+} else {
+ lockButton() // Authentication required
+}
+```
+
+The [`contentView()`](../../Shared/ContentView.swift#L169) function further decides:
+- If `chatModel.onboardingStage != .onboardingComplete`: show [onboarding](../../Shared/ContentView.swift#L174)
+- If `chatModel.migrationState != nil`: show migration UI
+- Otherwise: show `ChatListView` in a navigation container
+
+---
+
+## 3. Navigation Stack
+
+### iOS Version Compatibility
+
+**File**: [`Shared/Views/Helpers/NavStackCompat.swift`](../../Shared/Views/Helpers/NavStackCompat.swift)
+
+The app supports iOS 15+ and uses a compatibility wrapper ([`NavStackCompat`](../../Shared/Views/Helpers/NavStackCompat.swift#L11)):
+
+```swift
+// NavStackCompat provides:
+// - NavigationStack (iOS 16+): programmatic navigation via NavigationPath
+// - NavigationView (iOS 15): classic NavigationLink-based navigation
+```
+
+### Primary Navigation Path
+
+```
+ChatListView
+ │
+ ├─[tap chat]─→ ChatView
+ │ │
+ │ ├─[tap info]─→ ChatInfoView (direct)
+ │ │ └─→ VerifyCodeView, etc.
+ │ │
+ │ ├─[tap info]─→ GroupChatInfoView (group)
+ │ │ ├─→ GroupMemberInfoView
+ │ │ ├─→ GroupProfileView
+ │ │ └─→ GroupLinkView
+ │ │
+ │ └─[tap message info]─→ ChatItemInfoView
+ │
+ ├─[tap connection]─→ ContactConnectionInfo
+ │
+ └─[settings]─→ SettingsView
+ ├─→ NotificationsView
+ ├─→ NetworkAndServers
+ ├─→ AppearanceSettings
+ ├─→ PrivacySettings
+ ├─→ DatabaseView
+ └─→ UserProfilesView
+```
+
+### Navigation Trigger
+
+Chat navigation is triggered by setting `ChatModel.chatId`:
+
+```swift
+// In ChatListNavLink:
+ItemsModel.shared.loadOpenChat(chatId) {
+ // This sets ChatModel.chatId = chatId after a 250ms delay
+ // allowing navigation animation to start smoothly
+}
+```
+
+---
+
+## 4. Sheet Presentation
+
+Sheets are presented modally on top of the navigation stack:
+
+| Sheet | Trigger | Content |
+|-------|---------|---------|
+| UserPicker | Tap user avatar in nav bar | User list, settings shortcuts |
+| [`NewChatView`](../../Shared/Views/NewChat/NewChatView.swift#L78) | Tap FAB / "+" button | Create link, scan QR, paste link, new group |
+| WhatsNew | App update detected | Release notes |
+| AddGroupView | "New Group" action | Group creation wizard |
+| ConnectDesktopView | Settings > Desktop | Remote desktop pairing |
+| MigrateFromDevice | Settings > Migration | Device export |
+| MigrateToDevice | Onboarding migration | Device import |
+| [LocalAuthView](../../Shared/ContentView.swift#L95) | App foreground after background | Biometric/passcode auth |
+
+### Sheet Management
+
+Sheets use SwiftUI `.sheet(item:)` or `.sheet(isPresented:)` modifiers on `ContentView` and `ChatListView`. Some sheets use the centralized [`AppSheetState.shared`](../../Shared/ContentView.swift#L29) observable for coordination:
+
+```swift
+class AppSheetState: ObservableObject {
+ static let shared = AppSheetState()
+ var scenePhaseActive: Bool = false
+ // ... sheet state coordination
+}
+```
+
+---
+
+## 5. Deep Linking
+
+### Notification Deep Link
+
+When the user taps a notification:
+
+1. `NtfManager.processNotificationResponse()` extracts the `chatId` from notification payload
+2. If a different user: calls `changeActiveUser(userId:)`
+3. Sets `ChatModel.chatId = chatId` to navigate to the conversation
+4. If the app was in background: the notification response is stored in `ChatModel.notificationResponse` and processed when the app becomes active
+
+### [URL Deep Link](../../Shared/ContentView.swift#L281)
+
+SimpleX links (`simplex:/chat#...`) are handled via [`connectViaUrl()`](../../Shared/ContentView.swift#L439):
+
+```swift
+.onOpenURL { url in
+ if AppChatState.shared.value == .active {
+ chatModel.appOpenUrl = url // Process immediately
+ } else {
+ chatModel.appOpenUrlLater = url // Process when active
+ }
+}
+```
+
+URL processing routes to the appropriate connection flow (join group, add contact, etc.) via [`planAndConnect()`](../../Shared/Views/NewChat/NewChatView.swift#L1169).
+
+### Call Deep Link
+
+Call invitations from notifications:
+1. `NtfManager` detects `ntfActionAcceptCall` action
+2. Sets `ChatModel.ntfCallInvitationAction = (chatId, .accept)`
+3. `ContentView` picks up the pending action and initiates the call
+
+---
+
+## 6. Call Overlay
+
+The call UI overlays the entire app when a call is active:
+
+### [Call Banner](../../Shared/ContentView.swift#L203)
+
+When `ChatModel.activeCall != nil` and call is in connecting/active state:
+- A banner appears at the top of ContentView (height: [`callTopPadding = 40`](../../Shared/ContentView.swift#L54))
+- Shows contact name, call duration, tap to return to full-screen call
+- Main content is padded down to accommodate the banner
+
+### [Full-Screen Call View](../../Shared/ContentView.swift#L185)
+
+When `ChatModel.showCallView == true`:
+- `ActiveCallView` covers the entire screen as a ZStack overlay
+- Contains local/remote video, controls (mute, camera, speaker, end)
+- PiP mode: `ChatModel.activeCallViewIsCollapsed` collapses to mini view
+- Call view is always rendered on top of navigation and sheets
+
+```swift
+// In ContentView.allViews():
+ZStack {
+ contentView()
+ .padding(.top, showCallArea ? callTopPadding : 0)
+
+ if showCallArea, let call = chatModel.activeCall {
+ VStack {
+ activeCallInteractiveArea(call)
+ Spacer()
+ }
+ }
+
+ if chatModel.showCallView, let call = chatModel.activeCall {
+ callView(call) // Full screen overlay
+ }
+}
+```
+
+---
+
+## 7. Authentication Gate
+
+### [Local Authentication](../../Shared/ContentView.swift#L359)
+
+When [`DEFAULT_PERFORM_LA`](../../Shared/ContentView.swift#L44) is enabled:
+
+1. App enters background: `chatModel.contentViewAccessAuthenticated = false`
+2. App returns to foreground: `ContentView` shows [`lockButton()`](../../Shared/ContentView.swift#L238) instead of content
+3. User taps lock button: [`LocalAuthView`](../../Shared/ContentView.swift#L95) presented
+4. On successful auth: `chatModel.contentViewAccessAuthenticated = true`, content revealed
+
+### Authentication Methods
+- Face ID / Touch ID (via `LocalAuthentication` framework)
+- Custom numeric passcode
+- Custom alphanumeric passcode
+
+### [Extended Authentication](../../Shared/ContentView.swift#L351)
+- After successful auth, a grace period prevents re-auth for brief background/foreground cycles ([`unlockedRecently()`](../../Shared/ContentView.swift#L351))
+- [`contentAccessAuthenticationExtended`](../../Shared/ContentView.swift#L35) is computed at `ContentView.init` to avoid render-time race conditions
+- The `enteredBackgroundAuthenticated` timestamp tracks when the app was last authenticated in background
+
+---
+
+## 8. [Onboarding Flow](../../Shared/Views/Onboarding/OnboardingView.swift#L13)
+
+First-launch experience controlled by [`ChatModel.onboardingStage`](../../Shared/Views/Onboarding/OnboardingView.swift#L46):
+
+```swift
+enum OnboardingStage: String, Identifiable {
+ case step1_SimpleXInfo // Welcome screen
+ case step2_CreateProfile // deprecated
+ case step3_CreateSimpleXAddress // deprecated
+ case step3_ChooseServerOperators // Choose server operators
+ case step4_SetNotificationsMode // Set notification preferences
+ case onboardingComplete // Normal operation
+}
+```
+
+Each stage is a dedicated view presented in place of `ChatListView` within [`ContentView`](../../Shared/ContentView.swift#L174).
+
+Migration state (`ChatModel.migrationState != nil`) takes precedence over onboarding.
+
+---
+
+## Source Files
+
+| File | Path |
+|------|------|
+| Root view | [`Shared/ContentView.swift`](../../Shared/ContentView.swift) |
+| App entry point | `Shared/SimpleXApp.swift` |
+| Navigation compat | [`Shared/Views/Helpers/NavStackCompat.swift`](../../Shared/Views/Helpers/NavStackCompat.swift) |
+| Chat list (nav root) | `Shared/Views/ChatList/ChatListView.swift` |
+| Nav link wrapper | `Shared/Views/ChatList/ChatListNavLink.swift` |
+| User picker | `Shared/Views/ChatList/UserPicker.swift` |
+| New chat view | [`Shared/Views/NewChat/NewChatView.swift`](../../Shared/Views/NewChat/NewChatView.swift) |
+| Settings view | [`Shared/Views/UserSettings/SettingsView.swift`](../../Shared/Views/UserSettings/SettingsView.swift) |
+| User profiles | [`Shared/Views/UserSettings/UserProfilesView.swift`](../../Shared/Views/UserSettings/UserProfilesView.swift) |
+| Onboarding view | [`Shared/Views/Onboarding/OnboardingView.swift`](../../Shared/Views/Onboarding/OnboardingView.swift) |
+| Active call view | `Shared/Views/Call/ActiveCallView.swift` |
+| Local auth view | `Shared/Views/LocalAuth/LocalAuthView.swift` |
+| Notification manager | `Shared/Model/NtfManager.swift` |
diff --git a/apps/ios/spec/database.md b/apps/ios/spec/database.md
new file mode 100644
index 0000000000..9e5adfcb64
--- /dev/null
+++ b/apps/ios/spec/database.md
@@ -0,0 +1,298 @@
+# SimpleX Chat iOS -- Database & Storage
+
+**Source:** [`FileUtils.swift`](../SimpleXChat/FileUtils.swift)
+
+> Technical specification for the database architecture, encryption, file storage, and export/import functionality.
+>
+> Related specs: [Architecture](architecture.md) | [State Management](state.md) | [README](README.md)
+> Related product: [Product Overview](../product/README.md)
+
+---
+
+## Table of Contents
+
+1. [Database Overview](#1-database-overview)
+2. [Database Files & Paths](#2-database-files--paths)
+3. [Haskell Store Modules](#3-haskell-store-modules)
+4. [Migrations](#4-migrations)
+5. [Database Encryption](#5-database-encryption)
+6. [File Storage](#6-file-storage)
+7. [Export & Import](#7-export--import)
+8. [App Group Sharing](#8-app-group-sharing)
+
+---
+
+## 1. Database Overview
+
+SimpleX Chat uses two SQLite databases managed entirely by the Haskell core. The iOS Swift layer never reads or writes directly to the databases -- all data access goes through the FFI command/response API.
+
+| Database | Suffix | Contents |
+|----------|--------|----------|
+| Chat DB | `_chat.db` | Messages, contacts, groups, user profiles, files, tags, preferences, call history |
+| Agent DB | `_agent.db` | SMP agent connections, cryptographic keys, message queues, server state, XFTP chunks |
+
+Both databases are initialized and migrated via the C FFI function `chat_migrate_init_key()`, which applies pending migrations and returns a `chat_ctrl` pointer.
+
+---
+
+## 2. Database Files & Paths
+
+### [Path Resolution](../SimpleXChat/FileUtils.swift#L63-L73) (FileUtils.swift)
+
+```swift
+let DB_FILE_PREFIX = "simplex_v1"
+
+// Database path depends on container preference
+func getAppDatabasePath() -> URL {
+ dbContainerGroupDefault.get() == .group
+ ? getGroupContainerDirectory().appendingPathComponent(DB_FILE_PREFIX)
+ : getLegacyDatabasePath()
+}
+
+// Full database file paths:
+// Chat: {container}/simplex_v1_chat.db
+// Agent: {container}/simplex_v1_agent.db
+```
+
+### [File Constants](../SimpleXChat/FileUtils.swift#L38-L44)
+
+```swift
+let CHAT_DB: String = "_chat.db"
+let AGENT_DB: String = "_agent.db"
+private let CHAT_DB_BAK: String = "_chat.db.bak"
+private let AGENT_DB_BAK: String = "_agent.db.bak"
+```
+
+### Container Locations
+
+See [`getDocumentsDirectory()`](../SimpleXChat/FileUtils.swift#L47) and [`getGroupContainerDirectory()`](../SimpleXChat/FileUtils.swift#L52).
+
+| Container | Path | Used When |
+|-----------|------|-----------|
+| App Group | `FileManager.containerURL(forSecurityApplicationGroupIdentifier: APP_GROUP_NAME)` | Default (shared with NSE) |
+| Documents | `FileManager.urls(for: .documentDirectory)` | Legacy installations |
+
+The container choice is stored in `dbContainerGroupDefault` (`GroupDefaults`).
+
+---
+
+## 3. Haskell Store Modules
+
+All database operations are implemented in Haskell. Key store modules (paths relative to repo root):
+
+| Module | Path | Size | Description |
+|--------|------|------|-------------|
+| Messages | `src/Simplex/Chat/Store/Messages.hs` | ~178KB | Message CRUD, pagination, search, reactions, delivery receipts |
+| Groups | `src/Simplex/Chat/Store/Groups.hs` | ~126KB | Group CRUD, member management, roles, links, invitations |
+| Direct | `src/Simplex/Chat/Store/Direct.hs` | ~52KB | Direct contact connections, contact requests. See `createDirectChat` in `Store/Direct.hs` |
+| Files | `src/Simplex/Chat/Store/Files.hs` | ~43KB | File transfer state, XFTP chunks, inline files |
+| Profiles | `src/Simplex/Chat/Store/Profiles.hs` | ~42KB | User profiles, contact profiles, incognito profiles |
+| Connections | `src/Simplex/Chat/Store/Connections.hs` | ~17KB | Connection lifecycle, queue management |
+
+### Data Model (key tables)
+
+```
+users -- User profiles (userId, displayName, fullName, image, ...)
+contacts -- Contact records (contactId, userId, localDisplayName, ...)
+groups -- Group records (groupId, userId, groupProfile, ...)
+group_members -- Group membership (groupMemberId, groupId, memberId, role, ...)
+messages -- Message records (messageId, chatItemId, msgBody, ...)
+chat_items -- Chat items (chatItemId, chatType, chatId, content, ...)
+files -- File transfer records (fileId, chatItemId, fileName, fileSize, ...)
+connections -- SMP connections (connId, agentConnId, ...)
+chat_tags -- User-defined chat tags
+chat_tags_chats -- Tag-to-chat assignments
+```
+
+---
+
+## 4. Migrations
+
+Database migrations are managed by the Haskell core. Migration files are located in:
+
+```
+src/Simplex/Chat/Store/SQLite/Migrations/
+```
+
+Migrations are numbered sequentially starting from `M20220101` through `M20260122` (200+ migrations). Each migration is a Haskell module containing SQL statements for schema changes.
+
+The migration process:
+1. `chat_migrate_init_key()` is called with the database path
+2. Haskell reads the current schema version from the database
+3. Pending migrations are applied in order
+4. If migration fails, the function returns an error string (not a `chat_ctrl`)
+5. On success, a `chat_ctrl` pointer is returned
+
+Migration results are decoded in Swift as `DBMigrationResult`:
+- `.ok` -- migrations applied successfully
+- `.invalidConfirmation` -- migration requires user confirmation
+- `.errorNotADatabase(dbFile:)` -- file is not a valid SQLite database
+- `.errorMigration(dbFile:, migrationError:)` -- migration failed
+- `.errorSQL(dbFile:, migrationSQLError:)` -- SQL error during migration
+- `.errorKeychain` -- keychain access failed
+- `.unknown(json:)` -- unrecognized response
+
+---
+
+## 5. Database Encryption
+
+### Encryption Configuration
+
+Database encryption uses SQLCipher (AES-256) and is managed through the API:
+
+```swift
+// Set or change encryption
+ChatCommand.apiStorageEncryption(config: DBEncryptionConfig)
+
+// Test if a key is correct
+ChatCommand.testStorageEncryption(key: String)
+```
+
+`DBEncryptionConfig` contains:
+- `currentKey: String` -- current encryption key (empty if unencrypted)
+- `newKey: String` -- new encryption key (empty to decrypt)
+
+### Key Storage
+
+The encryption key is stored in the iOS Keychain via `kcDatabasePassword`:
+- On first launch with encryption, the key is generated and stored
+- The `storeDBPassphraseGroupDefault` flag controls whether the key is auto-stored
+- If the user opts out of auto-storage, they must enter the key on each launch
+
+### UI
+
+- [`DatabaseEncryptionView.swift`](../Shared/Views/Database/DatabaseEncryptionView.swift) -- Encryption settings UI
+- [`DatabaseView.swift`](../Shared/Views/Database/DatabaseView.swift) -- Database management UI (size, export, import, encryption)
+
+---
+
+## 6. File Storage
+
+### Directory Structure
+
+```
+{App Container}/
+├── Documents/
+│ ├── app_files/ -- Downloaded and sent files
+│ ├── temp_files/ -- Temporary files during transfer
+│ └── assets/wallpapers/ -- Custom wallpaper images
+├── {App Group Container}/
+│ ├── simplex_v1_chat.db -- Chat database
+│ ├── simplex_v1_agent.db -- Agent database
+│ └── ...
+```
+
+### [File Size Constants](../SimpleXChat/FileUtils.swift#L18-L36) (FileUtils.swift)
+
+```swift
+public let MAX_IMAGE_SIZE: Int64 = 261_120 // 255 KB -- inline image compression target
+public let MAX_IMAGE_SIZE_AUTO_RCV: Int64 = 522_240 // 510 KB -- auto-receive images
+public let MAX_VOICE_SIZE_AUTO_RCV: Int64 = 522_240 // 510 KB -- auto-receive voice
+public let MAX_VIDEO_SIZE_AUTO_RCV: Int64 = 1_047_552 // 1023 KB -- auto-receive video
+public let MAX_FILE_SIZE_XFTP: Int64 = 1_073_741_824 // 1 GB -- max XFTP transfer
+public let MAX_FILE_SIZE_SMP: Int64 = 8_000_000 // ~7.6 MB -- max SMP inline
+public let MAX_FILE_SIZE_LOCAL: Int64 = Int64.max // No limit for local files
+public let MAX_VOICE_MESSAGE_LENGTH = TimeInterval(300) // 5 minutes
+```
+
+### CryptoFile (Encrypted File Storage)
+
+When `apiSetEncryptLocalFiles(enable: true)` is set, files stored on device are AES-encrypted:
+
+- Encryption/decryption uses `chat_encrypt_file` / `chat_decrypt_file` C FFI functions
+- Each file gets a unique key and nonce stored alongside the file reference
+- The `CryptoFile` type wraps `(filePath: String, cryptoArgs: CryptoFileArgs?)` where `CryptoFileArgs` contains `(fileKey: String, fileNonce: String)`
+
+### [File Path Helpers](../SimpleXChat/FileUtils.swift#L219-L221)
+
+```swift
+public func getDocumentsDirectory() -> URL // Standard documents dir
+public func getGroupContainerDirectory() -> URL // App group container
+func getAppFilesDirectory() -> URL // {appDir}/app_files/
+func getTempFilesDirectory() -> URL // {appDir}/temp_files/
+func getWallpaperDirectory() -> URL // {appDir}/assets/wallpapers/
+```
+
+See also [`saveFile()`](../SimpleXChat/FileUtils.swift#L226), [`removeFile()`](../SimpleXChat/FileUtils.swift#L243), and [`getMaxFileSize()`](../SimpleXChat/FileUtils.swift#L276).
+
+### [Cleanup](../SimpleXChat/FileUtils.swift#L86-L116)
+
+- Files are deleted when their associated `ChatItem` is deleted. See [`cleanupFile()`](../SimpleXChat/FileUtils.swift#L267) and [`cleanupDirectFile()`](../SimpleXChat/FileUtils.swift#L260).
+- Timed message expiry triggers file deletion
+- [`deleteAppDatabaseAndFiles()`](../SimpleXChat/FileUtils.swift#L86) removes all databases, files, temp files, and wallpapers
+- [`deleteAppFiles()`](../SimpleXChat/FileUtils.swift#L108) removes only the files directory (preserving databases)
+
+---
+
+## 7. Export & Import
+
+### Export
+
+```swift
+ChatCommand.apiExportArchive(config: ArchiveConfig)
+// Response: ChatResponse2.archiveExported(archiveErrors: [ArchiveError])
+```
+
+`ArchiveConfig` specifies:
+- `archivePath: String` -- destination path for the archive
+- `disableCompression: Bool?` -- optional flag to skip compression
+
+The archive contains both databases and optionally files. The Haskell core handles the actual export, creating a ZIP archive.
+
+### Import
+
+```swift
+ChatCommand.apiImportArchive(config: ArchiveConfig)
+// Response: ChatResponse2.archiveImported(archiveErrors: [ArchiveError])
+```
+
+Import replaces the current databases with the archive contents. The app must be restarted after import.
+
+### Archive Errors
+
+`ArchiveError` is an array returned with both export and import results, listing any non-fatal issues encountered (e.g., missing files, corrupt entries).
+
+---
+
+## 8. App Group Sharing
+
+### Shared Access Model
+
+The main app and NSE share database access through the iOS App Group container:
+
+```
+Main App ──┐
+ ├── {App Group}/simplex_v1_chat.db
+ ├── {App Group}/simplex_v1_agent.db
+NSE ────────┘
+```
+
+### Coordination
+
+- Both processes can initialize their own `chat_ctrl` instance pointing to the same database files
+- SQLite WAL mode allows concurrent reads
+- Write coordination uses `chat_close_store` / `chat_reopen_store` to manage database locks
+- The main app suspends its chat controller when entering background, allowing NSE to access the database
+- NSE is short-lived (~30 seconds per notification) and releases its lock quickly
+
+### App State Communication
+
+The `appStateGroupDefault` in `GroupDefaults` communicates app state between main app and NSE:
+- `.active` -- main app is in foreground
+- `.suspended` -- main app is in background
+- `.stopped` -- main app is terminated
+
+The NSE checks this flag to determine whether to process notifications (it avoids processing if the main app is active).
+
+---
+
+## Source Files
+
+| File | Path |
+|------|------|
+| File utilities & constants | [`SimpleXChat/FileUtils.swift`](../SimpleXChat/FileUtils.swift) |
+| Database management UI | [`Shared/Views/Database/DatabaseView.swift`](../Shared/Views/Database/DatabaseView.swift) |
+| Encryption settings UI | [`Shared/Views/Database/DatabaseEncryptionView.swift`](../Shared/Views/Database/DatabaseEncryptionView.swift) |
+| C FFI (migration, file ops) | `SimpleXChat/SimpleX.h` |
+| Haskell store root | `../../src/Simplex/Chat/Store/` |
+| Haskell migrations | `../../src/Simplex/Chat/Store/SQLite/Migrations/` |
diff --git a/apps/ios/spec/impact.md b/apps/ios/spec/impact.md
new file mode 100644
index 0000000000..9593419b87
--- /dev/null
+++ b/apps/ios/spec/impact.md
@@ -0,0 +1,114 @@
+# SimpleX Chat iOS -- Impact Graph
+
+> Source file → product concept mapping. Use this to identify which product documents must be updated when a source file changes.
+>
+> Derived from [CODE.md](../CODE.md) Document Map and [product/concepts.md](../product/concepts.md).
+
+---
+
+## Product Concept Legend
+
+| ID | Concept |
+|----|---------|
+| PC1 | Chat List |
+| PC2 | Direct Chat |
+| PC3 | Group Chat |
+| PC4 | Message Composition |
+| PC5 | Message Reactions |
+| PC6 | Message Editing |
+| PC7 | Message Deletion |
+| PC8 | Timed Messages |
+| PC9 | Voice Messages |
+| PC10 | File Transfer |
+| PC11 | Link Previews |
+| PC12 | Contact Connection |
+| PC13 | Contact Verification |
+| PC14 | Group Management |
+| PC15 | Group Links |
+| PC16 | Member Roles |
+| PC17 | Audio/Video Calls |
+| PC18 | Push Notifications |
+| PC19 | User Profiles |
+| PC20 | Incognito Mode |
+| PC21 | Hidden Profiles |
+| PC22 | Local Authentication |
+| PC23 | Database Encryption |
+| PC24 | Theme System |
+| PC25 | Network Configuration |
+| PC26 | Device Migration |
+| PC27 | Remote Desktop |
+| PC28 | Chat Tags |
+| PC29 | User Address |
+| PC30 | Member Support Chat |
+
+---
+
+## 1. Swift Source Impact
+
+| Source File | Product Concepts Affected | Risk Level | Notes |
+|-------------|--------------------------|------------|-------|
+| Shared/ContentView.swift | PC1, PC2, PC3 | High | Root navigation — affects all chat access |
+| Shared/SimpleXApp.swift | PC1 through PC30 | High | App entry point — initialization affects everything |
+| Shared/AppDelegate.swift | PC18 | Medium | Push notification registration |
+| Shared/Views/ChatList/ChatListView.swift | PC1, PC28 | High | Main screen rendering and filtering |
+| Shared/Views/Chat/ChatView.swift | PC2, PC3, PC4, PC5, PC6, PC7, PC8, PC9, PC11 | High | Core conversation UI — most messaging features |
+| Shared/Views/Chat/ComposeMessage/ComposeView.swift | PC4, PC6, PC9, PC11 | High | Message composition — send path for all messages |
+| Shared/Views/Chat/ChatItem/ | PC2, PC3, PC5, PC7, PC8, PC9, PC10, PC11 | Medium | Individual message rendering components |
+| Shared/Views/Chat/ChatInfoView.swift | PC2, PC13, PC20 | Medium | Contact details and verification |
+| Shared/Views/Chat/Group/GroupChatInfoView.swift | PC3, PC14, PC15, PC16, PC30 | High | Group management hub |
+| Shared/Views/Chat/Group/AddGroupMembersView.swift | PC14, PC16 | Medium | Member invitation flow |
+| Shared/Views/Chat/Group/GroupLinkView.swift | PC15 | Low | Group link creation/sharing |
+| Shared/Views/Chat/Group/GroupMemberInfoView.swift | PC3, PC14, PC16, PC30 | Medium | Member details and role management |
+| Shared/Views/NewChat/NewChatView.swift | PC12 | High | New connection creation — onramp for all contacts |
+| Shared/Views/NewChat/QRCode.swift | PC12 | Low | QR code display/scanning utility |
+| Shared/Views/Call/ActiveCallView.swift | PC17 | Medium | Call UI rendering |
+| Shared/Views/Call/CallController.swift | PC17 | High | CallKit integration — call lifecycle |
+| Shared/Views/Call/WebRTCClient.swift | PC17 | High | WebRTC session management |
+| Shared/Views/UserSettings/SettingsView.swift | PC18, PC22, PC23, PC24, PC25, PC29 | Medium | Settings navigation hub |
+| Shared/Views/UserSettings/AppearanceSettings.swift | PC24 | Low | Theme customization UI |
+| Shared/Views/UserSettings/NetworkAndServers/ | PC25 | High | Server configuration — affects connectivity |
+| Shared/Views/UserSettings/UserProfilesView.swift | PC19, PC21 | Medium | Profile management |
+| Shared/Views/Onboarding/ | PC1 | Medium | First-time setup — affects initial state |
+| Shared/Views/LocalAuth/ | PC22 | Medium | App lock functionality |
+| Shared/Views/Database/ | PC23, PC26 | High | Database encryption and export |
+| Shared/Views/Migration/ | PC26 | High | Device migration — data portability |
+| Shared/Model/ChatModel.swift | PC1 through PC30 | High | Central state — all features depend on it |
+| Shared/Model/SimpleXAPI.swift | PC1 through PC30 | High | FFI bridge — all commands flow through here |
+| Shared/Model/AppAPITypes.swift | PC1 through PC30 | High | Command/response types — all API communication |
+| Shared/Model/NtfManager.swift | PC18 | High | Notification delivery |
+| Shared/Model/BGManager.swift | PC18 | Medium | Background fetch scheduling |
+| Shared/Theme/ThemeManager.swift | PC24 | Medium | Theme resolution engine |
+| SimpleXChat/ChatTypes.swift | PC1 through PC30 | High | Core data types — all features use them |
+| SimpleXChat/APITypes.swift | PC1 through PC30 | High | API result types and error handling |
+| SimpleXChat/CallTypes.swift | PC17 | Medium | Call-specific data types |
+| SimpleXChat/FileUtils.swift | PC10, PC23, PC26 | Medium | File paths and encryption utilities |
+| SimpleXChat/Notifications.swift | PC18 | Medium | Notification type definitions |
+| SimpleX NSE/NotificationService.swift | PC18 | High | Push notification decryption and display |
+
+---
+
+## 2. Haskell Core Impact
+
+| Source File | Product Concepts Affected | Risk Level | Notes |
+|-------------|--------------------------|------------|-------|
+| src/Simplex/Chat/Controller.hs | PC1 through PC30 | High | Command processor — all API commands |
+| 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 |
+| 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 |
+| src/Simplex/Chat/Call.hs | PC17 | Medium | Call signaling types |
+| src/Simplex/Chat/Files.hs | PC10 | Medium | File transfer orchestration |
+| 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 |
+| src/Simplex/Chat/Store/Files.hs | PC10 | Medium | File transfer persistence |
+| src/Simplex/Chat/Store/Profiles.hs | PC19, PC21 | Medium | User profile persistence |
+| src/Simplex/Chat/Store/Connections.hs | PC2, PC12 | High | Connection persistence and entity resolution |
+| src/Simplex/Chat/Archive.hs | PC26 | Medium | Database export/import for migration |
+| src/Simplex/Chat/ProfileGenerator.hs | PC20 | Low | Random profile generation for incognito |
+| src/Simplex/Chat/Remote.hs | PC27 | Medium | Remote desktop protocol handler |
+| src/Simplex/Chat/Remote/Types.hs | PC27 | Low | Remote desktop data types |
+| src/Simplex/Chat/Types/UITheme.hs | PC24 | Low | Theme data types for UI customization |
+| src/Simplex/Chat/Types/Preferences.hs | PC2, PC3, PC8 | Medium | Chat feature preferences (timed messages, etc.) |
+| src/Simplex/Chat/Types/Shared.hs | PC3, PC16 | Medium | Shared types including GroupMemberRole |
diff --git a/apps/ios/spec/services/calls.md b/apps/ios/spec/services/calls.md
new file mode 100644
index 0000000000..6a1d89f6a3
--- /dev/null
+++ b/apps/ios/spec/services/calls.md
@@ -0,0 +1,383 @@
+# SimpleX Chat iOS -- WebRTC Calling Service
+
+> Technical specification for the calling system: CallController, WebRTCClient, CallKit integration, and signaling via SMP.
+>
+> Related specs: [Architecture](../architecture.md) | [API Reference](../api.md) | [Notifications](notifications.md) | [README](../README.md)
+> Related product: [Chat View](../../product/views/chat.md)
+
+**Source:** [`CallController.swift`](../../Shared/Views/Call/CallController.swift) | [`WebRTCClient.swift`](../../Shared/Views/Call/WebRTCClient.swift) | [`ActiveCallView.swift`](../../Shared/Views/Call/ActiveCallView.swift) | [`CallTypes.swift`](../../SimpleXChat/CallTypes.swift)
+
+---
+
+## Table of Contents
+
+1. [Overview](#1-overview)
+2. [CallController](#2-callcontroller)
+3. [WebRTCClient](#3-webrtcclient)
+4. [Call Flow via SMP](#4-call-flow-via-smp)
+5. [CallKit Integration](#5-callkit-integration)
+6. [CallKit-Free Mode](#6-callkit-free-mode)
+7. [Audio Routing](#7-audio-routing)
+8. [Key Types](#8-key-types)
+9. [ActiveCallView](#9-activecallview)
+
+---
+
+## 1. Overview
+
+SimpleX Chat provides end-to-end encrypted audio and video calls using WebRTC. The unique aspect is that all call signaling (SDP offers/answers, ICE candidates) is transmitted through the same encrypted SMP messaging channels used for chat, eliminating the need for a separate signaling server.
+
+```
+Caller SMP Relay Callee
+ │ │ │
+ ├─ apiSendCallInvitation ──────→│──── push/event ──────→│
+ │ │ │
+ │ │←── apiSendCallOffer ──┤
+ │←── ChatEvent.callOffer ───────│ │
+ │ │ │
+ ├─ apiSendCallAnswer ──────────→│──── callAnswer ──────→│
+ │ │ │
+ │←── callExtraInfo (ICE) ───────│←── apiSendCallExtraInfo│
+ ├─ apiSendCallExtraInfo ───────→│──── callExtraInfo ───→│
+ │ │ │
+ │◄══════════ WebRTC P2P Media Stream ═══════════════════►│
+ │ │ │
+ ├─ apiEndCall ─────────────────→│──── callEnded ───────→│
+```
+
+---
+
+## [2. CallController](../../Shared/Views/Call/CallController.swift#L19-L449)
+
+**File**: `Shared/Views/Call/CallController.swift`
+
+Central call coordinator that bridges SimpleX call protocol with iOS CallKit (or non-CallKit fallback).
+
+### [Class Definition](../../Shared/Views/Call/CallController.swift#L19-L48)
+
+```swift
+class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, ObservableObject {
+ static let shared = CallController()
+ static let isInChina = SKStorefront().countryCode == "CHN"
+ static func useCallKit() -> Bool { !isInChina && callKitEnabledGroupDefault.get() }
+
+ private let provider: CXProvider // CallKit provider
+ private let controller: CXCallController // CallKit controller
+ private let callManager: CallManager // Internal call state
+ private let registry: PKPushRegistry // VoIP push registration
+
+ @Published var activeCallInvitation: RcvCallInvitation?
+ var shouldSuspendChat: Bool = false
+ var fulfillOnConnect: CXAnswerCallAction? = nil
+}
+```
+
+### Key Responsibilities
+
+| Method | Purpose | Line |
+|--------|---------|------|
+| [`reportNewIncomingCall()`](../../Shared/Views/Call/CallController.swift#L287) | Reports incoming call to CallKit for native UI | L287 |
+| [`reportOutgoingCall()`](../../Shared/Views/Call/CallController.swift#L328) | Reports outgoing call to CallKit | L328 |
+| [`provider(_:perform: CXAnswerCallAction)`](../../Shared/Views/Call/CallController.swift#L66) | Handles user answering via CallKit UI | L66 |
+| [`provider(_:perform: CXEndCallAction)`](../../Shared/Views/Call/CallController.swift#L96) | Handles user ending via CallKit UI | L96 |
+| [`provider(_:perform: CXStartCallAction)`](../../Shared/Views/Call/CallController.swift#L55) | Handles outgoing call start | L55 |
+| [`pushRegistry(_:didReceiveIncomingPushWith:)`](../../Shared/Views/Call/CallController.swift#L202) | Handles VoIP push tokens | L202 |
+| [`hasActiveCalls()`](../../Shared/Views/Call/CallController.swift#L435) | Checks if any calls are active | L435 |
+
+### Call Manager (internal)
+
+`CallManager` tracks call state internally:
+- Maps call UUIDs to `Call` objects
+- Handles call state transitions
+- Coordinates between CallKit actions and SimpleX API calls
+
+---
+
+## [3. WebRTCClient](../../Shared/Views/Call/WebRTCClient.swift#L13-L676)
+
+**File**: `Shared/Views/Call/WebRTCClient.swift` (~49KB)
+
+Manages the WebRTC peer connection, media streams, and data channels.
+
+### Responsibilities
+
+- Creates and configures `RTCPeerConnection`
+- Manages local audio/video capture (`RTCCameraVideoCapturer`, `RTCAudioTrack`)
+- Handles SDP offer/answer creation and application
+- Processes ICE candidates
+- Manages media stream encryption
+
+### Key Operations
+
+| Operation | Description | Line |
+|-----------|-------------|------|
+| [`initializeCall`](../../Shared/Views/Call/WebRTCClient.swift#L93) | Sets up peer connection, tracks, encryption | L93 |
+| [`createPeerConnection`](../../Shared/Views/Call/WebRTCClient.swift#L139) | Creates and configures RTCPeerConnection | L139 |
+| [`sendCallCommand`](../../Shared/Views/Call/WebRTCClient.swift#L176) | Dispatches WCallCommand (offer/answer/ICE) | L176 |
+| [`addIceCandidates`](../../Shared/Views/Call/WebRTCClient.swift#L165) | `peerConnection.add(RTCIceCandidate)` | L165 |
+| [`getInitialIceCandidates`](../../Shared/Views/Call/WebRTCClient.swift#L285) | Collects initial ICE candidates | L285 |
+| [`sendIceCandidates`](../../Shared/Views/Call/WebRTCClient.swift#L305) | Sends gathered ICE candidates | L305 |
+| [`enableMedia`](../../Shared/Views/Call/WebRTCClient.swift#L365) | Enable/disable audio or video track | L365 |
+| [`setupLocalTracks`](../../Shared/Views/Call/WebRTCClient.swift#L423) | Creates audio/video tracks and adds to connection | L423 |
+| [`startCaptureLocalVideo`](../../Shared/Views/Call/WebRTCClient.swift#L581) | Front/back camera toggle and capture start | L581 |
+| [`endCall`](../../Shared/Views/Call/WebRTCClient.swift#L645) | Tears down connection and tracks | L645 |
+| [`setupEncryptionForLocalTracks`](../../Shared/Views/Call/WebRTCClient.swift#L503) | Sets up frame encryption for local media tracks | L503 |
+
+### [Additional Encryption](../../Shared/Views/Call/WebRTCClient.swift#L513-L546)
+
+Beyond WebRTC's built-in SRTP encryption, SimpleX adds an extra encryption layer:
+- A shared key from the E2E SMP channel is used
+- Applied via `chat_encrypt_media` / `chat_decrypt_media` C FFI functions
+- Each media frame is encrypted/decrypted with this additional key
+- Provides defense-in-depth even if SRTP is compromised
+
+---
+
+## 4. Call Flow via SMP
+
+All call signaling travels through the same encrypted SMP message channels used for chat. No separate signaling server is needed.
+
+### Outgoing Call (Caller Side)
+
+```
+1. User initiates call
+ └── apiSendCallInvitation(contact:, callType:)
+ └── Sends CallInvitation via SMP to contact
+
+2. Callee accepts, sends SDP offer
+ └── ChatEvent.callOffer received
+ └── WebRTCClient creates answer
+ └── apiSendCallAnswer(contact:, answer:)
+
+3. ICE candidates exchanged
+ └── ChatEvent.callExtraInfo received → WebRTCClient.addIceCandidate()
+ └── WebRTCClient generates candidates → apiSendCallExtraInfo(contact:, extraInfo:)
+
+4. P2P connection established
+ └── Media streams flowing
+
+5. End call
+ └── apiEndCall(contact:)
+```
+
+### Incoming Call (Callee Side)
+
+```
+1. ChatEvent.callInvitation received (or push notification)
+ └── CallController reports to CallKit (or shows in-app notification)
+
+2. User accepts
+ └── WebRTCClient creates SDP offer (callee creates offer in SimpleX protocol)
+ └── apiSendCallOffer(contact:, callOffer:)
+
+3. Caller sends answer
+ └── ChatEvent.callAnswer received
+ └── WebRTCClient.setRemoteDescription(answer)
+
+4. ICE candidates exchanged (same as above)
+
+5. P2P connection established
+```
+
+### API Commands
+
+| Command | Direction | Purpose |
+|---------|-----------|---------|
+| `apiSendCallInvitation(contact:, callType:)` | Caller -> Callee | Initiate call |
+| `apiRejectCall(contact:)` | Callee -> Caller | Reject call |
+| `apiSendCallOffer(contact:, callOffer:)` | Callee -> Caller | Send SDP offer |
+| `apiSendCallAnswer(contact:, answer:)` | Caller -> Callee | Send SDP answer |
+| `apiSendCallExtraInfo(contact:, extraInfo:)` | Both | Send ICE candidates |
+| `apiEndCall(contact:)` | Either | End call |
+| `apiGetCallInvitations` | -- | Get pending invitations |
+| `apiCallStatus(contact:, callStatus:)` | -- | Report status change |
+
+---
+
+## [5. CallKit Integration](../../Shared/Views/Call/CallController.swift#L24-L155)
+
+CallKit provides the native iOS incoming call experience (lock screen UI, call history, system call handling).
+
+### [CXProvider Configuration](../../Shared/Views/Call/CallController.swift#L24-L37)
+
+```swift
+let configuration = CXProviderConfiguration()
+configuration.supportsVideo = true
+configuration.supportedHandleTypes = [.generic]
+configuration.includesCallsInRecents = UserDefaults.standard.bool(
+ forKey: DEFAULT_CALL_KIT_CALLS_IN_RECENTS
+)
+configuration.maximumCallGroups = 1
+configuration.maximumCallsPerCallGroup = 1
+configuration.iconTemplateImageData = UIImage(named: "icon-transparent")?.pngData()
+```
+
+### [VoIP Push (PKPushRegistry)](../../Shared/Views/Call/CallController.swift#L207-L284)
+
+CallKit requires VoIP push for incoming calls on locked device:
+- `PKPushRegistry` registers for `.voIP` push type
+- VoIP push token is separate from regular APNs token
+- When VoIP push received, **must** report an incoming call to CallKit within the callback (iOS requirement)
+
+### CallKit Actions
+
+| CXAction | Handler | Description | Line |
+|----------|---------|-------------|------|
+| `CXStartCallAction` | [`provider(_:perform:)`](../../Shared/Views/Call/CallController.swift#L55) | User starts outgoing call | L55 |
+| `CXAnswerCallAction` | [`provider(_:perform:)`](../../Shared/Views/Call/CallController.swift#L66) | User answers incoming call from CallKit UI | L66 |
+| `CXEndCallAction` | [`provider(_:perform:)`](../../Shared/Views/Call/CallController.swift#L96) | User ends call from CallKit UI | L96 |
+| `CXSetMutedCallAction` | [`provider(_:perform:)`](../../Shared/Views/Call/CallController.swift#L112) | User mutes from CallKit UI | L112 |
+
+### [Lock Screen Answer](../../Shared/Views/Call/CallController.swift#L66-L94)
+
+When answering from the lock screen:
+1. `CXAnswerCallAction` fires
+2. CallController waits for chat to be ready ([`waitUntilChatStarted(timeoutMs: 30_000)`](../../Shared/Views/Call/CallController.swift#L183))
+3. WebRTC connection established
+4. `fulfillOnConnect` action is fulfilled only when WebRTC reaches connected state (required for audio to work on lock screen)
+
+---
+
+## [6. CallKit-Free Mode](../../Shared/Views/Call/CallController.swift#L21-L22)
+
+In regions where CallKit is unavailable (e.g., China, determined by `SKStorefront.countryCode == "CHN"`), the app falls back to in-app notifications:
+
+```swift
+static let isInChina = SKStorefront().countryCode == "CHN"
+static func useCallKit() -> Bool { !isInChina && callKitEnabledGroupDefault.get() }
+```
+
+### Non-CallKit Behavior
+- Incoming calls shown as in-app banners (via `CallController.activeCallInvitation`)
+- No lock screen call UI
+- No system call integration
+- User can also manually disable CallKit via settings (`callKitEnabledGroupDefault`)
+
+---
+
+## [7. Audio Routing](../../Shared/Views/Call/WebRTCClient.swift#L907-L1005)
+
+### [AVAudioSession Management](../../Shared/Views/Call/WebRTCClient.swift#L907-L950)
+
+Audio routing is managed through `AVAudioSession`:
+- **Receiver**: Default for audio-only calls (ear speaker)
+- **Speaker**: For video calls or when user toggles speaker
+- **Bluetooth**: Detected and used when available
+- **Headphones**: Detected and used when connected
+
+### Route Change Handling
+
+The `WebRTCClient` observes `AVAudioSession.routeChangeNotification` to handle:
+- Bluetooth device connection/disconnection
+- Headphone plug/unplug
+- Speaker/receiver toggle
+
+---
+
+## [8. Key Types](../../SimpleXChat/CallTypes.swift#L1-L115)
+
+### [RcvCallInvitation](../../SimpleXChat/CallTypes.swift#L45-L71)
+
+```swift
+struct RcvCallInvitation {
+ var user: User
+ var contact: Contact
+ var callType: CallType
+ var sharedKey: String? // Optional E2E encryption key
+ var callUUID: String?
+ var callTs: Date
+}
+```
+
+### [CallType](../../SimpleXChat/CallTypes.swift#L74-L82)
+
+```swift
+struct CallType {
+ var media: CallMediaType // .audio or .video
+ var capabilities: CallCapabilities
+}
+
+enum CallMediaType: String {
+ case audio
+ case video
+}
+```
+
+### [WebRTCCallOffer](../../SimpleXChat/CallTypes.swift#L14-L22) / [WebRTCSession](../../SimpleXChat/CallTypes.swift#L25-L33)
+
+```swift
+struct WebRTCCallOffer {
+ var callType: CallType
+ var rtcSession: WebRTCSession
+}
+
+struct WebRTCSession {
+ var rtcSession: String // SDP string
+ var rtcIceCandidates: String // ICE candidates JSON
+}
+```
+
+### [WebRTCExtraInfo](../../SimpleXChat/CallTypes.swift#L36-L42)
+
+```swift
+struct WebRTCExtraInfo {
+ var rtcIceCandidates: String // Additional ICE candidates
+}
+```
+
+### Call (Active Call State)
+
+Stored in `ChatModel.activeCall`:
+- Contact reference
+- Call UUID
+- Call state (enum: `.waitCapabilities`, `.invitationAccepted`, `.offerSent`, `.answerReceived`, `.connected`, etc.)
+- Media type
+- WebRTCClient reference
+
+---
+
+## [9. ActiveCallView](../../Shared/Views/Call/ActiveCallView.swift#L16-L285)
+
+**File**: `Shared/Views/Call/ActiveCallView.swift`
+
+Full-screen call UI when `ChatModel.showCallView == true`:
+
+### UI Elements
+- Remote video (full screen background)
+- Local video (PiP corner, draggable)
+- Contact name and call duration
+- Control buttons: mute, camera toggle, speaker toggle, camera flip, end call
+- Minimize button (collapses to banner)
+
+### [ActiveCallOverlay](../../Shared/Views/Call/ActiveCallView.swift#L288-L522)
+
+| Control | Method | Line |
+|---------|--------|------|
+| Audio call info | [`audioCallInfoView`](../../Shared/Views/Call/ActiveCallView.swift#L357) | L357 |
+| Video call info | [`videoCallInfoView`](../../Shared/Views/Call/ActiveCallView.swift#L377) | L377 |
+| End call | [`endCallButton`](../../Shared/Views/Call/ActiveCallView.swift#L407) | L407 |
+| Mute toggle | [`toggleMicButton`](../../Shared/Views/Call/ActiveCallView.swift#L418) | L418 |
+| Audio device | [`audioDeviceButton`](../../Shared/Views/Call/ActiveCallView.swift#L428) | L428 |
+| Speaker toggle | [`toggleSpeakerButton`](../../Shared/Views/Call/ActiveCallView.swift#L452) | L452 |
+| Camera toggle | [`toggleCameraButton`](../../Shared/Views/Call/ActiveCallView.swift#L464) | L464 |
+| Flip camera | [`flipCameraButton`](../../Shared/Views/Call/ActiveCallView.swift#L475) | L475 |
+
+### PiP (Picture-in-Picture)
+
+When `ChatModel.activeCallViewIsCollapsed == true`:
+- Call view collapses to a small floating overlay
+- User can return to full-screen by tapping the banner
+- Navigation continues normally underneath
+
+---
+
+## Source Files
+
+| File | Path | Lines |
+|------|------|-------|
+| [Call controller](../../Shared/Views/Call/CallController.swift) | `Shared/Views/Call/CallController.swift` | 449 |
+| [WebRTC client](../../Shared/Views/Call/WebRTCClient.swift) | `Shared/Views/Call/WebRTCClient.swift` | 1139 |
+| [Active call UI](../../Shared/Views/Call/ActiveCallView.swift) | `Shared/Views/Call/ActiveCallView.swift` | 528 |
+| WebRTC helpers | `Shared/Views/Call/WebRTC.swift` | |
+| [Call types (Swift)](../../SimpleXChat/CallTypes.swift) | `SimpleXChat/CallTypes.swift` | 115 |
+| Call types (Haskell) | `../../src/Simplex/Chat/Call.hs` | |
diff --git a/apps/ios/spec/services/files.md b/apps/ios/spec/services/files.md
new file mode 100644
index 0000000000..7e1f8a2ad1
--- /dev/null
+++ b/apps/ios/spec/services/files.md
@@ -0,0 +1,368 @@
+# SimpleX Chat iOS -- File Transfer Service
+
+> Technical specification for file transfer: inline/XFTP protocols, auto-receive thresholds, CryptoFile encryption, and file constants.
+>
+> Related specs: [Compose Module](../client/compose.md) | [Chat View](../client/chat-view.md) | [API Reference](../api.md) | [Database](../database.md) | [README](../README.md)
+> Related product: [Product Overview](../../product/README.md)
+
+**Source:** [`FileUtils.swift`](../../SimpleXChat/FileUtils.swift) | [`CryptoFile.swift`](../../SimpleXChat/CryptoFile.swift) | [`ChatTypes.swift`](../../SimpleXChat/ChatTypes.swift) | [`AppAPITypes.swift`](../../Shared/Model/AppAPITypes.swift) | [`SimpleXAPI.swift`](../../Shared/Model/SimpleXAPI.swift)
+
+---
+
+## Table of Contents
+
+1. [Overview](#1-overview)
+2. [Transfer Methods](#2-transfer-methods)
+3. [Auto-Receive Thresholds](#3-auto-receive-thresholds)
+4. [File Size Constants](#4-file-size-constants)
+5. [Image Handling](#5-image-handling)
+6. [Voice Messages](#6-voice-messages)
+7. [CryptoFile -- At-Rest Encryption](#7-cryptofile)
+8. [File Storage Paths](#8-file-storage-paths)
+9. [File Lifecycle](#9-file-lifecycle)
+10. [API Commands](#10-api-commands)
+
+---
+
+## 1. Overview
+
+SimpleX Chat supports two file transfer methods depending on file size:
+
+```
+File ≤ 255KB (inline)
+├── Base64 encoded directly in SMP message
+├── Single message delivery
+└── No extra server infrastructure needed
+
+File > 255KB up to 1GB (XFTP)
+├── Encrypted and chunked
+├── Uploaded to XFTP relay servers
+├── Recipient downloads chunks from relays
+└── Files auto-deleted from relays after download or expiry
+```
+
+All files are end-to-end encrypted. The XFTP protocol adds a second encryption layer on top of the SMP channel encryption.
+
+---
+
+## 2. Transfer Methods
+
+### Inline Transfer
+
+- Files up to [`MAX_IMAGE_SIZE`](../../SimpleXChat/FileUtils.swift#L18) (255KB) are base64-encoded and embedded directly in the SMP message body
+- No additional protocol or server needed
+- Delivered with the same reliability guarantees as regular messages
+- Used primarily for compressed images
+
+### XFTP Transfer
+
+For files exceeding the inline threshold (up to [`MAX_FILE_SIZE_XFTP`](../../SimpleXChat/FileUtils.swift#L30) = 1GB):
+
+1. **Sender side**:
+ - File is AES-encrypted with a random key
+ - Encrypted file is split into chunks
+ - Chunks are uploaded to one or more XFTP relay servers
+ - File metadata (key, chunk locations) sent to recipient via SMP message
+
+2. **Recipient side**:
+ - Receives file metadata via SMP
+ - Downloads chunks from XFTP relays
+ - Reassembles and decrypts the file
+
+3. **Cleanup**:
+ - XFTP relays delete chunks after download or after expiry period
+ - No persistent storage on relays
+
+### SMP Transfer (legacy)
+
+[`MAX_FILE_SIZE_SMP`](../../SimpleXChat/FileUtils.swift#L34) (8MB) exists as a constant for larger inline transfers through SMP, used in specific scenarios.
+
+---
+
+## 3. Auto-Receive Thresholds
+
+Files below certain size thresholds are automatically accepted and downloaded without user confirmation:
+
+| Media Type | Auto-Receive Threshold | Constant | Line |
+|------------|----------------------|----------|------|
+| Images | 510 KB | [`MAX_IMAGE_SIZE_AUTO_RCV`](../../SimpleXChat/FileUtils.swift#L21) | [L21](../../SimpleXChat/FileUtils.swift#L21) |
+| Voice messages | 510 KB | [`MAX_VOICE_SIZE_AUTO_RCV`](../../SimpleXChat/FileUtils.swift#L24) | [L24](../../SimpleXChat/FileUtils.swift#L24) |
+| Video | 1023 KB | [`MAX_VIDEO_SIZE_AUTO_RCV`](../../SimpleXChat/FileUtils.swift#L27) | [L27](../../SimpleXChat/FileUtils.swift#L27) |
+| Other files | Not auto-received | Requires manual acceptance | -- |
+
+### Behavior
+
+- 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
+ 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)
+
+### Relay Approval
+
+`userApprovedRelays` parameter: when the file is hosted on relays not in the user's configured server list, the user is asked for confirmation before connecting to unknown relays.
+
+---
+
+## [4. File Size Constants](../../SimpleXChat/FileUtils.swift#L18)
+
+Defined in [`SimpleXChat/FileUtils.swift`](../../SimpleXChat/FileUtils.swift):
+
+| Constant | Value | Line |
+|----------|-------|------|
+| `MAX_IMAGE_SIZE` | 261,120 (255 KB) | [L18](../../SimpleXChat/FileUtils.swift#L18) |
+| `MAX_IMAGE_SIZE_AUTO_RCV` | 522,240 (510 KB) | [L21](../../SimpleXChat/FileUtils.swift#L21) |
+| `MAX_VOICE_SIZE_AUTO_RCV` | 522,240 (510 KB) | [L24](../../SimpleXChat/FileUtils.swift#L24) |
+| `MAX_VIDEO_SIZE_AUTO_RCV` | 1,047,552 (1023 KB) | [L27](../../SimpleXChat/FileUtils.swift#L27) |
+| `MAX_FILE_SIZE_XFTP` | 1,073,741,824 (1 GB) | [L30](../../SimpleXChat/FileUtils.swift#L30) |
+| `MAX_FILE_SIZE_LOCAL` | Int64.max (no limit) | [L32](../../SimpleXChat/FileUtils.swift#L32) |
+| `MAX_FILE_SIZE_SMP` | 8,000,000 (~7.6 MB) | [L34](../../SimpleXChat/FileUtils.swift#L34) |
+| `MAX_VOICE_MESSAGE_LENGTH` | 300 s (5 min) | [L36](../../SimpleXChat/FileUtils.swift#L36) |
+
+```swift
+// Image compression target for inline transfer
+public let MAX_IMAGE_SIZE: Int64 = 261_120 // 255 KB
+
+// Auto-receive thresholds
+public let MAX_IMAGE_SIZE_AUTO_RCV: Int64 = 522_240 // 510 KB (2 * MAX_IMAGE_SIZE)
+public let MAX_VOICE_SIZE_AUTO_RCV: Int64 = 522_240 // 510 KB (2 * MAX_IMAGE_SIZE)
+public let MAX_VIDEO_SIZE_AUTO_RCV: Int64 = 1_047_552 // 1023 KB
+
+// Transfer method limits
+public let MAX_FILE_SIZE_XFTP: Int64 = 1_073_741_824 // 1 GB
+public let MAX_FILE_SIZE_SMP: Int64 = 8_000_000 // ~7.6 MB
+public let MAX_FILE_SIZE_LOCAL: Int64 = Int64.max // No limit (local notes)
+
+// Voice message constraints
+public let MAX_VOICE_MESSAGE_LENGTH = TimeInterval(300) // 5 minutes (300 seconds)
+```
+
+---
+
+## 5. Image Handling
+
+### Compression Pipeline
+
+1. User selects image (camera or photo library)
+2. Image is compressed to fit within [`MAX_IMAGE_SIZE`](../../SimpleXChat/FileUtils.swift#L18) (255KB):
+ - Progressive JPEG compression with decreasing quality
+ - Resize if dimensions are too large
+3. Compressed image is base64-encoded into the message content
+4. For larger images that cannot compress to 255KB: sent via XFTP
+
+### Display
+
+- `CIImageView` renders images in chat bubbles with aspect-fit sizing
+- Tapping opens `FullScreenMediaView` with zoom/pan/share capabilities
+- Thumbnail is displayed immediately; full-size loaded on demand for XFTP images
+
+### Animated Images
+
+- GIFs are handled by `AnimatedImageView`
+- Displayed inline with animation support
+
+---
+
+## 6. Voice Messages
+
+### Recording
+
+1. `ComposeVoiceView` manages the recording UI
+2. `AudioRecPlay` handles `AVAudioRecorder` lifecycle
+3. Recorded in compressed audio format
+4. Maximum duration: [`MAX_VOICE_MESSAGE_LENGTH`](../../SimpleXChat/FileUtils.swift#L36) = 300 seconds (5 minutes)
+5. Waveform data extracted for visualization
+
+### Transfer
+
+- Voice files up to [`MAX_VOICE_SIZE_AUTO_RCV`](../../SimpleXChat/FileUtils.swift#L24) (510KB) are auto-received
+- Larger voice files follow standard file transfer flow
+- Voice messages include waveform metadata for UI rendering
+
+### Playback
+
+- `CIVoiceView` / `FramedCIVoiceView` render voice messages
+- Shows waveform visualization and play/pause control
+- `ChatModel.stopPreviousRecPlay` ensures only one audio source plays at a time
+- Playback position and progress tracked
+
+---
+
+## [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.
+
+### [`CryptoFile`](../../SimpleXChat/ChatTypes.swift#L4241) Type
+
+```swift
+struct CryptoFile {
+ var filePath: String
+ var cryptoArgs: CryptoFileArgs? // nil = unencrypted
+}
+
+struct CryptoFileArgs {
+ var fileKey: String // AES encryption key
+ var fileNonce: String // AES nonce/IV
+}
+```
+
+> Defined in [`ChatTypes.swift` L4241](../../SimpleXChat/ChatTypes.swift#L4241) (`CryptoFile`) and [L4289](../../SimpleXChat/ChatTypes.swift#L4289) (`CryptoFileArgs`).
+
+### Encryption Operations (C FFI)
+
+Implemented in [`CryptoFile.swift`](../../SimpleXChat/CryptoFile.swift):
+
+| Function | Purpose | Line |
+|----------|---------|------|
+| [`writeCryptoFile`](../../SimpleXChat/CryptoFile.swift#L18) | Write encrypted file, returns `CryptoFileArgs` | [L18](../../SimpleXChat/CryptoFile.swift#L18) |
+| [`readCryptoFile`](../../SimpleXChat/CryptoFile.swift#L31) | Read and decrypt file, returns `Data` | [L31](../../SimpleXChat/CryptoFile.swift#L31) |
+| [`encryptCryptoFile`](../../SimpleXChat/CryptoFile.swift#L54) | Encrypt existing file to new path | [L54](../../SimpleXChat/CryptoFile.swift#L54) |
+| [`decryptCryptoFile`](../../SimpleXChat/CryptoFile.swift#L66) | Decrypt file to new path | [L66](../../SimpleXChat/CryptoFile.swift#L66) |
+
+### Storage
+
+- 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)
+
+---
+
+## [8. File Storage Paths](../../SimpleXChat/FileUtils.swift#L199)
+
+### Directory Structure
+
+| Function | Path | Line |
+|----------|------|------|
+| [`getAppFilesDirectory()`](../../SimpleXChat/FileUtils.swift#L208) | `Documents/files/` | [L208](../../SimpleXChat/FileUtils.swift#L208) |
+| [`getTempFilesDirectory()`](../../SimpleXChat/FileUtils.swift#L199) | `Documents/temp_files/` | [L199](../../SimpleXChat/FileUtils.swift#L199) |
+| [`getWallpaperDirectory()`](../../SimpleXChat/FileUtils.swift#L217) | `Documents/wallpapers/` | [L217](../../SimpleXChat/FileUtils.swift#L217) |
+| [`getAppFilePath(_:)`](../../SimpleXChat/FileUtils.swift#L212) | `Documents/files/{filename}` | [L212](../../SimpleXChat/FileUtils.swift#L212) |
+| [`getWallpaperFilePath(_:)`](../../SimpleXChat/FileUtils.swift#L221) | `Documents/wallpapers/{filename}` | [L221](../../SimpleXChat/FileUtils.swift#L221) |
+
+```swift
+func getAppFilesDirectory() -> URL // Documents/files/
+func getTempFilesDirectory() -> URL // Documents/temp_files/
+func getWallpaperDirectory() -> URL // Documents/wallpapers/
+```
+
+### Path Management
+
+- 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
+
+---
+
+## 9. File Lifecycle
+
+### Sending
+
+```
+1. User selects file/image/video in compose
+2. ComposeView creates ComposedMessage with file reference
+3. apiSendMessages() → Haskell core processes:
+ a. File ≤ inline threshold: base64 encode into message
+ b. File > inline threshold: start XFTP upload
+4. Upload events:
+ - ChatEvent.sndFileStart
+ - ChatEvent.sndFileProgressXFTP (periodic progress)
+ - ChatEvent.sndFileCompleteXFTP (upload done)
+ - ChatEvent.sndFileError (on failure)
+```
+
+### Receiving
+
+```
+1. Message with file attachment arrives
+2. Auto-receive check:
+ a. Below threshold: automatic download starts
+ b. Above threshold: user sees download button
+3. User triggers download (or auto-triggered):
+ - receiveFile(fileId:, userApprovedRelays:, encrypted:, inline:)
+4. Download events:
+ - ChatEvent.rcvFileStart
+ - ChatEvent.rcvFileProgressXFTP (periodic progress)
+ - ChatEvent.rcvFileComplete (download done)
+ - ChatEvent.rcvFileError (on failure)
+ - ChatEvent.rcvFileSndCancelled (sender cancelled)
+```
+
+### Cancellation
+
+```swift
+ChatCommand.cancelFile(fileId: Int64)
+```
+
+Cancels an in-progress upload or download. For XFTP transfers, also requests chunk deletion from relays.
+
+### Cleanup
+
+| Function | Purpose | Line |
+|----------|---------|------|
+| [`cleanupFile(_:)`](../../SimpleXChat/FileUtils.swift#L267) | Remove file associated with a chat item | [L267](../../SimpleXChat/FileUtils.swift#L267) |
+| [`cleanupDirectFile(_:)`](../../SimpleXChat/FileUtils.swift#L260) | Remove file only for direct chats | [L260](../../SimpleXChat/FileUtils.swift#L260) |
+| [`removeFile(_:)`](../../SimpleXChat/FileUtils.swift#L243) | Delete file at URL | [L243](../../SimpleXChat/FileUtils.swift#L243) |
+| [`removeFile(_:)`](../../SimpleXChat/FileUtils.swift#L251) | Delete file by name | [L251](../../SimpleXChat/FileUtils.swift#L251) |
+| [`deleteAppFiles()`](../../SimpleXChat/FileUtils.swift#L108) | Remove all app files (preserving databases) | [L108](../../SimpleXChat/FileUtils.swift#L108) |
+| [`deleteAppDatabaseAndFiles()`](../../SimpleXChat/FileUtils.swift#L86) | Remove everything | [L86](../../SimpleXChat/FileUtils.swift#L86) |
+
+- When a `ChatItem` is deleted, its associated file is deleted from disk
+- When a timed message expires, its file is deleted
+- `ChatModel.filesToDelete` queues files for deferred deletion
+- [`deleteAppFiles()`](../../SimpleXChat/FileUtils.swift#L108) removes all files (preserving databases)
+- [`deleteAppDatabaseAndFiles()`](../../SimpleXChat/FileUtils.swift#L86) removes everything
+
+---
+
+## [10. API Commands](../../Shared/Model/AppAPITypes.swift#L167)
+
+| 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) |
+
+### 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) |
+
+---
+
+## Source Files
+
+| File | Path | Key Definitions |
+|------|------|-----------------|
+| File utilities & constants | [`SimpleXChat/FileUtils.swift`](../../SimpleXChat/FileUtils.swift) | `MAX_IMAGE_SIZE`, `saveFile`, `removeFile`, `getMaxFileSize` |
+| 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) |
+| 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) | |
+| Voice view (chat item) | [`Shared/Views/Chat/ChatItem/CIVoiceView.swift`](../../Shared/Views/Chat/ChatItem/CIVoiceView.swift) | |
+| Compose file preview | [`Shared/Views/Chat/ComposeMessage/ComposeFileView.swift`](../../Shared/Views/Chat/ComposeMessage/ComposeFileView.swift) | |
+| Compose image preview | [`Shared/Views/Chat/ComposeMessage/ComposeImageView.swift`](../../Shared/Views/Chat/ComposeMessage/ComposeImageView.swift) | |
+| Compose voice preview | [`Shared/Views/Chat/ComposeMessage/ComposeVoiceView.swift`](../../Shared/Views/Chat/ComposeMessage/ComposeVoiceView.swift) | |
+| C FFI (file encryption) | [`SimpleXChat/SimpleX.h`](../../SimpleXChat/SimpleX.h) | `chat_write_file`, `chat_read_file`, `chat_encrypt_file`, `chat_decrypt_file` |
+| Haskell file logic | `../../src/Simplex/Chat/Files.hs` | -- |
+| Haskell file store | `../../src/Simplex/Chat/Store/Files.hs` | -- |
diff --git a/apps/ios/spec/services/notifications.md b/apps/ios/spec/services/notifications.md
new file mode 100644
index 0000000000..1062833f9c
--- /dev/null
+++ b/apps/ios/spec/services/notifications.md
@@ -0,0 +1,390 @@
+# SimpleX Chat iOS -- Push Notification Service
+
+> Technical specification for the notification system: NtfManager, Notification Service Extension (NSE), notification modes, and token lifecycle.
+>
+> Related specs: [Architecture](../architecture.md) | [API Reference](../api.md) | [Navigation](../client/navigation.md) | [README](../README.md)
+> Related product: [Product Overview](../../product/README.md)
+
+**Source:** [`NtfManager.swift`](../../Shared/Model/NtfManager.swift) | [`BGManager.swift`](../../Shared/Model/BGManager.swift) | [`Notifications.swift`](../../SimpleXChat/Notifications.swift) | [`NotificationService.swift`](../../SimpleX NSE/NotificationService.swift)
+
+---
+
+## Table of Contents
+
+1. [Overview](#1-overview)
+2. [Notification Modes](#2-notification-modes)
+3. [NtfManager](#3-ntfmanager)
+4. [Notification Service Extension (NSE)](#4-notification-service-extension)
+5. [Token Lifecycle](#5-token-lifecycle)
+6. [Notification Categories & Actions](#6-notification-categories--actions)
+7. [Badge Management](#7-badge-management)
+8. [Background Tasks (BGManager)](#8-background-tasks)
+
+---
+
+## 1. Overview
+
+SimpleX Chat uses a privacy-preserving notification architecture. Because messages are end-to-end encrypted and the notification server never sees message content, the app uses a Notification Service Extension (NSE) to decrypt push payloads on-device before displaying notifications.
+
+```
+APNs Push → NSE receives encrypted payload
+ → NSE starts Haskell core (own chat_ctrl)
+ → NSE decrypts message using stored keys
+ → NSE creates UNNotificationContent with decrypted preview
+ → iOS displays notification to user
+```
+
+The notification system has three modes of operation, allowing users to choose their privacy/convenience tradeoff.
+
+---
+
+## 2. Notification Modes
+
+| Mode | Description | Mechanism |
+|------|-------------|-----------|
+| **Instant** | Real-time notifications via Apple Push | APNs push triggers NSE, which decrypts and displays |
+| **Periodic** | Background fetch every ~20 minutes | `BGAppRefreshTask` wakes app, checks for new messages |
+| **Off** | No notifications | User must open app to see messages |
+
+### Configuration
+
+Notification mode is set via:
+```swift
+ChatCommand.apiRegisterToken(token: DeviceToken, notificationMode: NotificationsMode)
+```
+
+`NotificationsMode` enum: `.instant`, `.periodic`, `.off`
+
+The mode is stored in `ChatModel.notificationMode` and persisted in `GroupDefaults`.
+
+---
+
+## 3. NtfManager
+
+**File**: [`Shared/Model/NtfManager.swift`](../../Shared/Model/NtfManager.swift)
+
+Central notification coordinator. Singleton: `NtfManager.shared`.
+
+### [Class Definition](../../Shared/Model/NtfManager.swift#L27)
+
+```swift
+class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
+ static let shared = NtfManager()
+ public var navigatingToChat = false
+ private var granted = false
+ private var prevNtfTime: Dictionary = [:]
+}
+```
+
+### Key Responsibilities
+
+| Method | Purpose | Line |
+|--------|---------|------|
+| [`registerCategories()`](../../Shared/Model/NtfManager.swift#L156) | Registers notification action categories with iOS | [156](../../Shared/Model/NtfManager.swift#L156) |
+| [`requestAuthorization()`](../../Shared/Model/NtfManager.swift#L215) | Requests notification permission from user | [215](../../Shared/Model/NtfManager.swift#L215) |
+| [`setNtfBadgeCount(_:)`](../../Shared/Model/NtfManager.swift#L264) | Updates app icon badge | [264](../../Shared/Model/NtfManager.swift#L264) |
+| [`processNotificationResponse(_:)`](../../Shared/Model/NtfManager.swift#L54) | Handles user interaction with notification | [54](../../Shared/Model/NtfManager.swift#L54) |
+| [`notifyContactRequest(_:)`](../../Shared/Model/NtfManager.swift#L239) | Shows contact request notification | [239](../../Shared/Model/NtfManager.swift#L239) |
+| [`notifyCallInvitation(_:)`](../../Shared/Model/NtfManager.swift#L258) | Shows incoming call notification | [258](../../Shared/Model/NtfManager.swift#L258) |
+| [`notifyMessageReceived(_:)`](../../Shared/Model/NtfManager.swift#L250) | Shows message received notification | [250](../../Shared/Model/NtfManager.swift#L250) |
+
+### [Notification Response Processing](../../Shared/Model/NtfManager.swift#L40)
+
+When user taps a notification:
+
+1. `userNotificationCenter(didReceive:)` delegate method fires
+2. If app is active: calls `processNotificationResponse()` immediately
+3. If app is inactive: stores in `ChatModel.notificationResponse` for later processing
+4. [`processNotificationResponse()`](../../Shared/Model/NtfManager.swift#L54):
+ - Extracts `userId` from `userInfo` -- switches user if needed
+ - Extracts `chatId` -- navigates to the conversation
+ - Handles action identifiers (accept contact, accept/reject call)
+
+### [Rate Limiting](../../Shared/Model/NtfManager.swift#L144)
+
+`prevNtfTime` dictionary prevents notification flooding:
+- Each chat has a timestamp of its last notification
+- New notifications are suppressed if within `ntfTimeInterval` (1 second) of the previous one for the same chat
+
+---
+
+## 4. Notification Service Extension (NSE)
+
+**File**: [`SimpleX NSE/NotificationService.swift`](../../SimpleX NSE/NotificationService.swift)
+
+### Architecture
+
+The NSE is a separate process that iOS launches when a push notification arrives. It has:
+- Its own Haskell runtime instance (`chat_ctrl`)
+- Shared database access (via app group container)
+- ~30 second execution window per notification
+- No access to main app's in-memory state
+
+### [Processing Flow](../../SimpleX NSE/NotificationService.swift#L300)
+
+```
+1. didReceive(request:, withContentHandler:) L300
+ ├── 2. Initialize Haskell core (if not already running)
+ │ └── chat_migrate_init_key() with shared DB path L861
+ ├── 3. Decode encrypted notification payload
+ │ └── apiGetNtfConns(nonce:, encNtfInfo:) L1123
+ ├── 4. Fetch and decrypt messages
+ │ └── apiGetConnNtfMessages(connMsgReqs:) L1140
+ ├── 5. Create notification content
+ │ ├── Contact name as title
+ │ ├── Decrypted message preview as body
+ │ └── Thread identifier for grouping
+ └── 6. Deliver to content handler
+```
+
+### NSE Commands
+
+The NSE uses a subset of the chat API:
+
+| Command | Purpose | Line |
+|---------|---------|------|
+| [`apiGetNtfConns(nonce:, encNtfInfo:)`](../../SimpleX NSE/NotificationService.swift#L1123) | Decrypt notification connection info | [1123](../../SimpleX NSE/NotificationService.swift#L1123) |
+| [`apiGetConnNtfMessages(connMsgReqs:)`](../../SimpleX NSE/NotificationService.swift#L1140) | Fetch messages for notification connections | [1140](../../SimpleX NSE/NotificationService.swift#L1140) |
+
+### Database Coordination
+
+- NSE checks `appStateGroupDefault` before processing
+- 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)
+
+`NotificationPreviewMode` controls what the NSE shows:
+
+| Mode | Title | Body |
+|------|-------|------|
+| `.message` | Contact name | Message text |
+| `.contact` | Contact name | "New message" |
+| `.hidden` | "SimpleX" | "New message" |
+
+### Key Internal Types
+
+| Type | Purpose | Line |
+|------|---------|------|
+| [`NSENotificationData`](../../SimpleX NSE/NotificationService.swift#L27) | Enum of possible notification payloads | [27](../../SimpleX NSE/NotificationService.swift#L27) |
+| [`NSEThreads`](../../SimpleX NSE/NotificationService.swift#L82) | Concurrency coordinator for multiple NSE instances | [82](../../SimpleX NSE/NotificationService.swift#L82) |
+| [`NotificationEntity`](../../SimpleX NSE/NotificationService.swift#L245) | Per-connection processing state | [245](../../SimpleX NSE/NotificationService.swift#L245) |
+| [`NotificationService`](../../SimpleX NSE/NotificationService.swift#L287) | Main NSE class (`UNNotificationServiceExtension`) | [287](../../SimpleX NSE/NotificationService.swift#L287) |
+| [`NSEChatState`](../../SimpleX NSE/NotificationService.swift#L781) | Singleton managing NSE lifecycle state | [781](../../SimpleX NSE/NotificationService.swift#L781) |
+
+### Key Internal Functions
+
+| Function | Purpose | Line |
+|----------|---------|------|
+| [`startChat()`](../../SimpleX NSE/NotificationService.swift#L836) | Initializes Haskell core for NSE | [836](../../SimpleX NSE/NotificationService.swift#L836) |
+| [`doStartChat()`](../../SimpleX NSE/NotificationService.swift#L861) | Performs actual chat initialization (migration, config) | [861](../../SimpleX NSE/NotificationService.swift#L861) |
+| [`activateChat()`](../../SimpleX NSE/NotificationService.swift#L907) | Reactivates suspended chat controller | [907](../../SimpleX NSE/NotificationService.swift#L907) |
+| [`suspendChat(_:)`](../../SimpleX NSE/NotificationService.swift#L921) | Suspends chat controller with timeout | [921](../../SimpleX NSE/NotificationService.swift#L921) |
+| [`receiveMessages()`](../../SimpleX NSE/NotificationService.swift#L954) | Main message-receive loop | [954](../../SimpleX NSE/NotificationService.swift#L954) |
+| [`receivedMsgNtf(_:)`](../../SimpleX NSE/NotificationService.swift#L1003) | Maps chat events to notification data | [1003](../../SimpleX NSE/NotificationService.swift#L1003) |
+| [`receiveNtfMessages(_:)`](../../SimpleX NSE/NotificationService.swift#L403) | Orchestrates notification message fetch and delivery | [403](../../SimpleX NSE/NotificationService.swift#L403) |
+| [`deliverBestAttemptNtf()`](../../SimpleX NSE/NotificationService.swift#L604) | Delivers the best available notification content | [604](../../SimpleX NSE/NotificationService.swift#L604) |
+| [`didReceive(_:withContentHandler:)`](../../SimpleX%20NSE/NotificationService.swift#L300) | Main NSE entry point -- processes incoming notification | [300](../../SimpleX%20NSE/NotificationService.swift#L300) |
+
+---
+
+## 5. Token Lifecycle
+
+### Registration Flow
+
+```
+1. App starts → AppDelegate.didRegisterForRemoteNotificationsWithDeviceToken
+ └── ChatModel.deviceToken = token
+
+2. Token registration (when chat running and token available):
+ └── apiRegisterToken(token, notificationMode)
+ └── Response: ntfToken(token, status, ntfMode, ntfServer)
+ └── ChatModel.tokenStatus = status
+
+3. Token verification (if server requires):
+ └── apiVerifyToken(token, nonce, code)
+ └── ChatModel.tokenRegistered = true
+
+4. Token check (periodic):
+ └── apiCheckToken(token)
+ └── Updates ChatModel.tokenStatus
+```
+
+### Token States (NtfTknStatus)
+
+| Status | Description |
+|--------|-------------|
+| `.new` | Token just registered, not yet verified |
+| `.registered` | Token registered with notification server |
+| `.confirmed` | Token confirmed and ready |
+| `.active` | Token actively receiving notifications |
+| `.expired` | Token expired, needs re-registration |
+| `.invalid` | Token invalid, needs new registration |
+| `.invalidBad` | Token invalid due to bad data |
+| `.invalidTopic` | Token invalid due to wrong topic |
+| `.invalidExpired` | Token invalid because it expired |
+| `.invalidUnregistered` | Token invalid, was unregistered |
+
+### Token Deletion
+
+```swift
+ChatCommand.apiDeleteToken(token: DeviceToken)
+```
+
+Called when:
+- User switches to `.off` notification mode
+- User deletes their profile
+- Token becomes invalid and needs replacement
+
+---
+
+## 6. Notification Categories & Actions
+
+Registered in [`NtfManager.registerCategories()`](../../Shared/Model/NtfManager.swift#L156):
+
+### Contact Request Category
+
+```swift
+// Category: "NTF_CAT_CONTACT_REQUEST"
+// Actions:
+// - "NTF_ACT_ACCEPT_CONTACT": Accept contact request
+```
+
+When user taps "Accept" on a contact request notification:
+1. `processNotificationResponse()` detects `ntfActionAcceptContact`
+2. Calls `apiAcceptContact(incognito: false, contactReqId:)`
+3. Navigates to the new contact's chat
+
+### Call Invitation Category
+
+```swift
+// Category: "NTF_CAT_CALL_INVITATION"
+// Actions:
+// - "NTF_ACT_ACCEPT_CALL": Accept incoming call
+// - "NTF_ACT_REJECT_CALL": Reject incoming call
+```
+
+When user taps "Accept" / "Reject" on a call notification:
+1. `processNotificationResponse()` detects the action
+2. Sets `ChatModel.ntfCallInvitationAction = (chatId, .accept/.reject)`
+3. Call controller picks up the pending action
+
+### Message Category
+
+Standard tap-to-open behavior navigates to the chat.
+
+### Many Events Category
+
+Batch notification for multiple events -- navigates to the app without specific chat context.
+
+---
+
+## 7. Badge Management
+
+The app icon badge shows the total unread message count:
+
+```swift
+// Updated when:
+// 1. App enters background:
+NtfManager.shared.setNtfBadgeCount(chatModel.totalUnreadCountForAllUsers())
+
+// 2. Messages are read:
+// Badge is recalculated and updated
+
+// 3. NSE receives notification:
+// NSE updates badge based on its count
+```
+
+`totalUnreadCountForAllUsers()` sums unread counts across all user profiles (not just the active user).
+
+### NSE Badge Handling
+
+| Method | Purpose | Line |
+|--------|---------|------|
+| [`setBadgeCount()`](../../SimpleX NSE/NotificationService.swift#L592) | Increments badge via `ntfBadgeCountGroupDefault` | [592](../../SimpleX NSE/NotificationService.swift#L592) |
+| [`setNtfBadgeCount(_:)`](../../Shared/Model/NtfManager.swift#L264) | Sets badge on `UIApplication` | [264](../../Shared/Model/NtfManager.swift#L264) |
+| [`changeNtfBadgeCount(by:)`](../../Shared/Model/NtfManager.swift#L270) | Adjusts badge by delta | [270](../../Shared/Model/NtfManager.swift#L270) |
+
+---
+
+## 8. Background Tasks
+
+**File**: [`Shared/Model/BGManager.swift`](../../Shared/Model/BGManager.swift)
+
+### [BGManager](../../Shared/Model/BGManager.swift#L30)
+
+```swift
+class BGManager {
+ static let shared = BGManager()
+ func register() // Register BGAppRefreshTask handlers
+ func schedule() // Schedule next background refresh
+}
+```
+
+| Method | Purpose | Line |
+|--------|---------|------|
+| [`register()`](../../Shared/Model/BGManager.swift#L38) | Registers `BGAppRefreshTask` handler with iOS | [38](../../Shared/Model/BGManager.swift#L38) |
+| [`schedule()`](../../Shared/Model/BGManager.swift#L46) | Schedules next background refresh request | [46](../../Shared/Model/BGManager.swift#L46) |
+| [`handleRefresh(_:)`](../../Shared/Model/BGManager.swift#L74) | Processes background refresh task | [74](../../Shared/Model/BGManager.swift#L74) |
+| [`completionHandler(_:)`](../../Shared/Model/BGManager.swift#L95) | Creates completion callback with cleanup | [95](../../Shared/Model/BGManager.swift#L95) |
+| [`receiveMessages(_:)`](../../Shared/Model/BGManager.swift#L112) | Activates chat and receives pending messages | [112](../../Shared/Model/BGManager.swift#L112) |
+
+### Background Refresh (Periodic Mode)
+
+When notification mode is `.periodic`:
+
+1. `BGManager.schedule()` is called when app enters background
+2. iOS wakes the app in the background approximately every 20 minutes
+3. `BGAppRefreshTask` handler:
+ - Activates the chat engine: `apiActivateChat(restoreChat: true)`
+ - Checks for new messages
+ - Creates local notifications for any new messages
+ - Suspends chat: `apiSuspendChat(timeoutMicroseconds:)`
+ - Schedules next refresh
+4. Must complete within ~30 seconds or iOS terminates the task
+
+### Background Task Protection
+
+All API calls use `beginBGTask()` / `endBackgroundTask()` to request extra execution time:
+
+```swift
+func beginBGTask(_ handler: (() -> Void)? = nil) -> (() -> Void) {
+ var id: UIBackgroundTaskIdentifier!
+ // ...
+ id = UIApplication.shared.beginBackgroundTask(expirationHandler: endTask)
+ return endTask
+}
+```
+
+Maximum task duration: `maxTaskDuration = 15` seconds.
+
+---
+
+## Notification Content Builders
+
+**File**: [`SimpleXChat/Notifications.swift`](../../SimpleXChat/Notifications.swift)
+
+| Function | Purpose | Line |
+|----------|---------|------|
+| [`createContactRequestNtf()`](../../SimpleXChat/Notifications.swift#L27) | Builds notification for incoming contact request | [L27](../../SimpleXChat/Notifications.swift#L27) |
+| [`createContactConnectedNtf()`](../../SimpleXChat/Notifications.swift#L46) | Builds notification for contact connected event | [L46](../../SimpleXChat/Notifications.swift#L46) |
+| [`createMessageReceivedNtf()`](../../SimpleXChat/Notifications.swift#L66) | Builds notification for received message | [L66](../../SimpleXChat/Notifications.swift#L66) |
+| [`createCallInvitationNtf()`](../../SimpleXChat/Notifications.swift#L86) | Builds notification for incoming call | [L86](../../SimpleXChat/Notifications.swift#L86) |
+| [`createConnectionEventNtf()`](../../SimpleXChat/Notifications.swift#L102) | Builds notification for connection events | [L102](../../SimpleXChat/Notifications.swift#L102) |
+| [`createErrorNtf()`](../../SimpleXChat/Notifications.swift#L134) | Builds notification for database/encryption errors | [L134](../../SimpleXChat/Notifications.swift#L134) |
+| [`createAppStoppedNtf()`](../../SimpleXChat/Notifications.swift#L160) | Builds notification when app is stopped | [L160](../../SimpleXChat/Notifications.swift#L160) |
+| [`createNotification()`](../../SimpleXChat/Notifications.swift#L175) | Generic notification builder (used by all above) | [L175](../../SimpleXChat/Notifications.swift#L175) |
+| [`hideSecrets()`](../../SimpleXChat/Notifications.swift#L200) | Redacts secret-formatted text in previews | [L200](../../SimpleXChat/Notifications.swift#L200) |
+
+---
+
+## Source Files
+
+| File | Path |
+|------|------|
+| Notification manager | [`Shared/Model/NtfManager.swift`](../../Shared/Model/NtfManager.swift) |
+| Background manager | [`Shared/Model/BGManager.swift`](../../Shared/Model/BGManager.swift) |
+| Notification types | [`SimpleXChat/Notifications.swift`](../../SimpleXChat/Notifications.swift) |
+| NSE service | [`SimpleX NSE/NotificationService.swift`](../../SimpleX NSE/NotificationService.swift) |
+| App delegate (token) | `Shared/AppDelegate.swift` |
+| Notification settings UI | `Shared/Views/UserSettings/NotificationsView.swift` |
diff --git a/apps/ios/spec/services/theme.md b/apps/ios/spec/services/theme.md
new file mode 100644
index 0000000000..321f3307f9
--- /dev/null
+++ b/apps/ios/spec/services/theme.md
@@ -0,0 +1,383 @@
+# SimpleX Chat iOS -- Theme Engine
+
+> Technical specification for the theming system: ThemeManager, default themes, customization layers, wallpapers, and YAML export.
+>
+> Related specs: [State Management](../state.md) | [Architecture](../architecture.md) | [README](../README.md)
+> Related product: [Product Overview](../../product/README.md)
+
+**Source:** [`ThemeManager.swift`](../../Shared/Theme/ThemeManager.swift) | [`AppearanceSettings.swift`](../../Shared/Views/UserSettings/AppearanceSettings.swift) | [`ThemeTypes.swift`](../../SimpleXChat/Theme/ThemeTypes.swift) | [`ChatWallpaperTypes.swift`](../../SimpleXChat/Theme/ChatWallpaperTypes.swift) | [`Theme.swift`](../../Shared/Theme/Theme.swift)
+
+---
+
+## Table of Contents
+
+1. [Overview](#1-overview)
+2. [ThemeManager](#2-thememanager)
+3. [Default Themes](#3-default-themes)
+4. [Customization Layers](#4-customization-layers)
+5. [Color System](#5-color-system)
+6. [Wallpapers](#6-wallpapers)
+7. [Chat Bubble Styling](#7-chat-bubble-styling)
+8. [Color Scheme Mode](#8-color-scheme-mode)
+9. [YAML Export/Import](#9-yaml-exportimport)
+
+---
+
+## 1. Overview
+
+The theme engine provides a layered customization system where themes can be overridden at multiple levels: global defaults, per-user, and per-chat.
+
+```
+Theme Resolution Order (most specific wins):
+┌─────────────────────┐
+│ Per-chat override │ apiSetChatUIThemes(chatId:, themes:)
+├─────────────────────┤
+│ Per-user override │ apiSetUserUIThemes(userId:, themes:)
+├─────────────────────┤
+│ App settings theme │ themeOverridesDefault (UserDefaults)
+├─────────────────────┤
+│ Base theme │ Light / Dark / SimpleX / Black
+└─────────────────────┘
+```
+
+The resolved theme is published as `AppTheme.shared` and consumed by all SwiftUI views via `@EnvironmentObject`.
+
+---
+
+## 2. [ThemeManager](../../Shared/Theme/ThemeManager.swift) (L15)
+
+**File**: [`Shared/Theme/ThemeManager.swift`](../../Shared/Theme/ThemeManager.swift)
+
+Static utility class that resolves the current theme by merging all customization layers.
+
+### [ActiveTheme](../../Shared/Theme/ThemeManager.swift#L17)
+
+The resolved theme output:
+
+```swift
+struct ActiveTheme: Equatable {
+ let name: String // Theme name (e.g., "light", "dark", "simplex", "black", "system")
+ let base: DefaultTheme // Base theme enum
+ let colors: Colors // Resolved color palette
+ let appColors: AppColors // App-specific colors (sent/received bubbles, etc.)
+ var wallpaper: AppWallpaper // Resolved wallpaper
+}
+```
+
+### Key Static Methods
+
+| Method | Purpose | Line |
+|--------|---------|------|
+| [`applyTheme(_:)`](../../Shared/Theme/ThemeManager.swift#L124) | Apply a theme by name, updates `AppTheme.shared` | [L124](../../Shared/Theme/ThemeManager.swift#L124) |
+| [`currentColors(...)`](../../Shared/Theme/ThemeManager.swift#L64) | Resolve full theme from all layers | [L64](../../Shared/Theme/ThemeManager.swift#L64) |
+| [`defaultActiveTheme(_:)`](../../Shared/Theme/ThemeManager.swift#L48) | Get default theme override from app settings | [L48](../../Shared/Theme/ThemeManager.swift#L48) |
+| [`currentThemeOverridesForExport(...)`](../../Shared/Theme/ThemeManager.swift#L105) | Get current overrides for YAML export | [L105](../../Shared/Theme/ThemeManager.swift#L105) |
+| [`adjustWindowStyle()`](../../Shared/Theme/ThemeManager.swift#L136) | Adjust window style after theme change | [L136](../../Shared/Theme/ThemeManager.swift#L136) |
+| [`changeDarkTheme(_:)`](../../Shared/Theme/ThemeManager.swift#L166) | Change the dark theme variant | [L166](../../Shared/Theme/ThemeManager.swift#L166) |
+| [`saveAndApplyThemeColor(...)`](../../Shared/Theme/ThemeManager.swift#L173) | Save and apply a theme color override | [L173](../../Shared/Theme/ThemeManager.swift#L173) |
+| [`applyThemeColor(...)`](../../Shared/Theme/ThemeManager.swift#L186) | Apply a theme color to a binding | [L186](../../Shared/Theme/ThemeManager.swift#L186) |
+| [`saveAndApplyWallpaper(...)`](../../Shared/Theme/ThemeManager.swift#L191) | Save and apply a wallpaper change | [L191](../../Shared/Theme/ThemeManager.swift#L191) |
+| [`copyFromSameThemeOverrides(...)`](../../Shared/Theme/ThemeManager.swift#L213) | Copy overrides from matching theme | [L213](../../Shared/Theme/ThemeManager.swift#L213) |
+| [`applyWallpaper(...)`](../../Shared/Theme/ThemeManager.swift#L256) | Apply wallpaper to a binding | [L256](../../Shared/Theme/ThemeManager.swift#L256) |
+| [`saveAndApplyThemeOverrides(...)`](../../Shared/Theme/ThemeManager.swift#L267) | Save and apply full theme overrides | [L267](../../Shared/Theme/ThemeManager.swift#L267) |
+| [`resetAllThemeColors(_:)`](../../Shared/Theme/ThemeManager.swift#L288) | Reset all color overrides (CodableDefault) | [L288](../../Shared/Theme/ThemeManager.swift#L288) |
+| [`resetAllThemeColors(_:)`](../../Shared/Theme/ThemeManager.swift#L302) | Reset all color overrides (Binding) | [L302](../../Shared/Theme/ThemeManager.swift#L302) |
+| [`removeTheme(_:)`](../../Shared/Theme/ThemeManager.swift#L311) | Remove a saved theme by ID | [L311](../../Shared/Theme/ThemeManager.swift#L311) |
+
+### Theme Resolution Algorithm
+
+[`currentColors()`](../../Shared/Theme/ThemeManager.swift#L64) in `ThemeManager.swift`:
+
+1. Determine base theme from `currentThemeDefault`:
+ - If `"system"`: use light or dark based on [`systemInDarkThemeCurrently`](../../Shared/Theme/Theme.swift#L95)
+ - Dark mode maps to `systemDarkThemeDefault` (Dark, SimpleX, or Black)
+2. Get base color palette ([`LightColorPalette`](../../SimpleXChat/Theme/ThemeTypes.swift#L650), [`DarkColorPalette`](../../SimpleXChat/Theme/ThemeTypes.swift#L629), [`SimplexColorPalette`](../../SimpleXChat/Theme/ThemeTypes.swift#L671), [`BlackColorPalette`](../../SimpleXChat/Theme/ThemeTypes.swift#L692))
+3. Look up app settings theme override (`themeOverridesDefault`)
+4. Look up per-user theme override (`User.uiThemes`)
+5. Look up per-chat theme override (from ChatInfo)
+6. Look up wallpaper preset colors (if wallpaper has preset color overrides)
+7. Merge layers: base <- app override <- preset wallpaper colors <- per-user <- per-chat
+8. Return `ActiveTheme` with resolved colors, app colors, and wallpaper
+
+---
+
+## 3. Default Themes
+
+Four built-in themes with pre-defined color palettes:
+
+| Theme | Enum | Key Characteristics |
+|-------|------|---------------------|
+| **Light** | `DefaultTheme.LIGHT` | White background, standard colors |
+| **Dark** | `DefaultTheme.DARK` | Dark gray background, light text |
+| **SimpleX** | `DefaultTheme.SIMPLEX` | Brand purple accents, dark background |
+| **Black** | `DefaultTheme.BLACK` | Pure black background (OLED), high contrast |
+
+### [DefaultTheme](../../SimpleXChat/Theme/ThemeTypes.swift#L13) Enum
+
+```swift
+enum DefaultTheme {
+ case LIGHT
+ case DARK
+ case SIMPLEX
+ case BLACK
+
+ static let SYSTEM_THEME_NAME = "SYSTEM"
+
+ var themeName: String { ... }
+ var mode: DefaultThemeMode { ... } // .light or .dark
+}
+```
+
+### Color Palettes
+
+Each base theme defines two palette types:
+- [`Colors`](../../SimpleXChat/Theme/ThemeTypes.swift#L44): Standard UI colors (primary, background, surface, error, onBackground, onSurface)
+- [`AppColors`](../../SimpleXChat/Theme/ThemeTypes.swift#L90): App-specific colors (sentMessage, receivedMessage, title, primaryVariant2)
+
+Palette instances:
+- [`LightColorPalette`](../../SimpleXChat/Theme/ThemeTypes.swift#L650) / [`LightColorPaletteApp`](../../SimpleXChat/Theme/ThemeTypes.swift#L662)
+- [`DarkColorPalette`](../../SimpleXChat/Theme/ThemeTypes.swift#L629) / [`DarkColorPaletteApp`](../../SimpleXChat/Theme/ThemeTypes.swift#L641)
+- [`SimplexColorPalette`](../../SimpleXChat/Theme/ThemeTypes.swift#L671) / [`SimplexColorPaletteApp`](../../SimpleXChat/Theme/ThemeTypes.swift#L683)
+- [`BlackColorPalette`](../../SimpleXChat/Theme/ThemeTypes.swift#L692) / [`BlackColorPaletteApp`](../../SimpleXChat/Theme/ThemeTypes.swift#L704)
+
+---
+
+## 4. Customization Layers
+
+### Layer 1: App Settings Theme
+
+Stored in `themeOverridesDefault` (UserDefaults). Contains `[ThemeOverrides]` -- an array of theme overrides, one per base theme.
+
+#### [`ThemeOverrides`](../../SimpleXChat/Theme/ThemeTypes.swift#L385)
+
+```swift
+struct ThemeOverrides: Codable {
+ var base: DefaultTheme
+ var colors: ThemeColors? // Color overrides
+ var wallpaper: ThemeWallpaper? // Wallpaper setting
+}
+```
+
+### Layer 2: Per-User Theme
+
+Stored on the `User` object (`User.uiThemes: ThemeModeOverrides?`), persisted in the Haskell database via `apiSetUserUIThemes(userId:, themes:)`.
+
+#### [`ThemeModeOverrides`](../../SimpleXChat/Theme/ThemeTypes.swift#L570)
+
+```swift
+struct ThemeModeOverrides: Codable {
+ var light: ThemeModeOverride?
+ var dark: ThemeModeOverride?
+}
+```
+
+#### [`ThemeModeOverride`](../../SimpleXChat/Theme/ThemeTypes.swift#L585)
+
+```swift
+struct ThemeModeOverride: Codable {
+ var mode: DefaultThemeMode?
+ var colors: ThemeColors?
+ var wallpaper: ThemeWallpaper?
+ var type: WallpaperType? // Computed from wallpaper
+}
+```
+
+### Layer 3: Per-Chat Theme
+
+Stored per-chat via `apiSetChatUIThemes(chatId:, themes:)`. Same `ThemeModeOverrides` structure.
+
+### Override Merging
+
+Colors are merged field-by-field: if a more-specific layer defines a color, it overrides; if nil, falls through to the next layer.
+
+---
+
+## 5. Color System
+
+**File**: [`SimpleXChat/Theme/ThemeTypes.swift`](../../SimpleXChat/Theme/ThemeTypes.swift)
+
+### [ThemeColors](../../SimpleXChat/Theme/ThemeTypes.swift#L230)
+
+Overridable color definitions:
+
+```swift
+struct ThemeColors: Codable {
+ var primary: String? // Primary brand color
+ var primaryVariant: String? // Primary variant
+ var secondary: String? // Secondary color
+ var secondaryVariant: String? // Secondary variant
+ var background: String? // Main background
+ var surface: String? // Card/surface background
+ var title: String? // Title text color
+ var primaryVariant2: String? // Additional variant
+ var sentMessage: String? // Sent message bubble
+ var sentQuote: String? // Sent quote background
+ var receivedMessage: String? // Received message bubble
+ var receivedQuote: String? // Received quote background
+}
+```
+
+Colors are stored as hex strings (e.g., `"#FF6600"`) and converted to SwiftUI `Color` values at resolution time.
+
+### [Colors](../../SimpleXChat/Theme/ThemeTypes.swift#L44) (Resolved Palette)
+
+```swift
+struct Colors {
+ var isLight: Bool
+ var primary: Color
+ var primaryVariant: Color
+ var secondary: Color
+ var secondaryVariant: Color
+ var background: Color
+ var surface: Color
+ var error: Color
+ var onBackground: Color
+ var onSurface: Color
+ // ... etc
+}
+```
+
+### [AppColors](../../SimpleXChat/Theme/ThemeTypes.swift#L90) (Resolved App-Specific)
+
+```swift
+struct AppColors {
+ var title: Color
+ var primaryVariant2: Color
+ var sentMessage: Color
+ var sentQuote: Color
+ var receivedMessage: Color
+ var receivedQuote: Color
+}
+```
+
+---
+
+## 6. Wallpapers
+
+**File**: [`SimpleXChat/Theme/ChatWallpaperTypes.swift`](../../SimpleXChat/Theme/ChatWallpaperTypes.swift)
+
+### [Preset Wallpapers](../../SimpleXChat/Theme/ChatWallpaperTypes.swift#L13)
+
+6 built-in wallpaper presets:
+
+| Preset | ID | Description |
+|--------|-----|-------------|
+| Cats | `cats` | Cat-themed pattern |
+| Flowers | `flowers` | Floral pattern |
+| Hearts | `hearts` | Heart pattern |
+| Kids | `kids` | Children's pattern |
+| School | `school` | School/notebook pattern (default) |
+| Travel | `travel` | Travel-themed pattern |
+
+Each preset defines per-theme color tints (`PresetWallpaper.colors[DefaultTheme]`) that subtly adjust the color palette to complement the wallpaper.
+
+### Custom Wallpapers
+
+Users can set a custom image as wallpaper:
+- Stored in `Documents/wallpapers/` directory
+- Scaled and tiled to fill the chat background
+- Custom wallpapers can be combined with color overrides
+
+### [WallpaperType](../../SimpleXChat/Theme/ChatWallpaperTypes.swift#L311)
+
+```swift
+enum WallpaperType {
+ case preset(filename: String, scale: Float?) // Built-in wallpaper
+ case image(filename: String, scale: Float?) // Custom image
+ case empty // No wallpaper
+}
+```
+
+### [AppWallpaper](../../SimpleXChat/Theme/ThemeTypes.swift#L142) (Resolved)
+
+```swift
+struct AppWallpaper {
+ var background: Color? // Background color override
+ var tint: Color? // Tint/overlay color
+ var type: WallpaperType
+}
+```
+
+---
+
+## 7. Chat Bubble Styling
+
+Configurable bubble appearance properties:
+
+| Property | Description | Stored In |
+|----------|-------------|-----------|
+| `chatItemRoundness` | Corner radius of message bubbles | App settings |
+| `chatItemTail` | Whether bubbles have a tail/arrow | App settings |
+| Avatar corner radius | Roundness of profile avatars | App settings |
+
+These are configured in [`Shared/Views/UserSettings/AppearanceSettings.swift`](../../Shared/Views/UserSettings/AppearanceSettings.swift) ([L26](../../Shared/Views/UserSettings/AppearanceSettings.swift#L26)).
+
+---
+
+## 8. Color Scheme Mode
+
+### System Follow
+
+When theme is set to `"system"` (DefaultTheme.SYSTEM_THEME_NAME):
+- Light mode: uses `DefaultTheme.LIGHT` palette
+- Dark mode: uses the configured dark theme (`systemDarkThemeDefault`), which can be Dark, SimpleX, or Black
+
+### Forced Mode
+
+Users can force light or dark mode regardless of system setting by selecting a specific theme other than "system".
+
+### Detection
+
+[`systemInDarkThemeCurrently`](../../Shared/Theme/Theme.swift#L95):
+
+```swift
+var systemInDarkThemeCurrently: Bool {
+ return UITraitCollection.current.userInterfaceStyle == .dark
+}
+```
+
+`ChatModel.currentUser` setter triggers [`ThemeManager.applyTheme()`](../../Shared/Theme/ThemeManager.swift#L124) to handle per-user theme overrides when switching users.
+
+---
+
+## 9. YAML Export/Import
+
+Theme configurations can be exported as YAML for sharing:
+
+### Export
+
+[`ThemeManager.currentThemeOverridesForExport()`](../../Shared/Theme/ThemeManager.swift#L105) generates a `ThemeOverrides` representing the current resolved theme, which is then serialized to YAML using the Yams library.
+
+### Import
+
+YAML theme strings are parsed back into `ThemeOverrides` and applied as app settings theme overrides.
+
+Key functions in [`AppearanceSettings.swift`](../../Shared/Views/UserSettings/AppearanceSettings.swift):
+
+| Function | Purpose | Line |
+|----------|---------|------|
+| [`ImportExportThemeSection`](../../Shared/Views/UserSettings/AppearanceSettings.swift#L603) | UI section for import/export controls | [L603](../../Shared/Views/UserSettings/AppearanceSettings.swift#L603) |
+| [`ThemeImporter`](../../Shared/Views/UserSettings/AppearanceSettings.swift#L640) | ViewModifier for YAML file import | [L640](../../Shared/Views/UserSettings/AppearanceSettings.swift#L640) |
+| [`decodeYAML(_:)`](../../Shared/Views/UserSettings/AppearanceSettings.swift#L1150) | Parse YAML string into Decodable type | [L1150](../../Shared/Views/UserSettings/AppearanceSettings.swift#L1150) |
+| [`encodeThemeOverrides(_:)`](../../Shared/Views/UserSettings/AppearanceSettings.swift#L1160) | Encode ThemeOverrides to YAML string | [L1160](../../Shared/Views/UserSettings/AppearanceSettings.swift#L1160) |
+
+### Toolbar Material
+
+[`ToolbarMaterial`](../../Shared/Views/UserSettings/AppearanceSettings.swift#L319) controls the navigation bar appearance:
+- Configurable opacity/material (translucent, opaque)
+- Stored in app settings
+
+---
+
+## Source Files
+
+| File | Path | Key Definitions |
+|------|------|-----------------|
+| Theme manager | [`Shared/Theme/ThemeManager.swift`](../../Shared/Theme/ThemeManager.swift) | `ThemeManager` (L15), `ActiveTheme` (L17) |
+| Theme types & colors | [`SimpleXChat/Theme/ThemeTypes.swift`](../../SimpleXChat/Theme/ThemeTypes.swift) | `DefaultTheme` (L13), `Colors` (L44), `AppColors` (L90), `AppWallpaper` (L142), `ThemeColors` (L230), `ThemeWallpaper` (L302), `ThemeOverrides` (L385), `ThemeModeOverrides` (L570), `ThemeModeOverride` (L585) |
+| Wallpaper types | [`SimpleXChat/Theme/ChatWallpaperTypes.swift`](../../SimpleXChat/Theme/ChatWallpaperTypes.swift) | `PresetWallpaper` (L13), `WallpaperType` (L311) |
+| Color utilities | [`SimpleXChat/Theme/Color.swift`](../../SimpleXChat/Theme/Color.swift) | Hex color conversion |
+| App theme observable | [`Shared/Theme/Theme.swift`](../../Shared/Theme/Theme.swift) | `AppTheme` (L22), `CurrentColors` (L14), `systemInDarkThemeCurrently` (L95) |
+| Appearance settings UI | [`Shared/Views/UserSettings/AppearanceSettings.swift`](../../Shared/Views/UserSettings/AppearanceSettings.swift) | `AppearanceSettings` (L26), `ToolbarMaterial` (L319), `ImportExportThemeSection` (L603) |
+| Theme mode editor | `Shared/Views/Helpers/ThemeModeEditor.swift` | Theme mode selection UI |
+| Haskell theme types | `../../src/Simplex/Chat/Types/UITheme.hs` | Server-side theme persistence |
diff --git a/apps/ios/spec/state.md b/apps/ios/spec/state.md
new file mode 100644
index 0000000000..68b5f3cbcc
--- /dev/null
+++ b/apps/ios/spec/state.md
@@ -0,0 +1,463 @@
+# SimpleX Chat iOS -- State Management
+
+**Source:** [`ChatModel.swift`](../Shared/Model/ChatModel.swift#L1-L1375) | [`ChatTypes.swift`](../SimpleXChat/ChatTypes.swift#L1-L5284)
+
+> Technical specification for the app's state architecture: ChatModel, ItemsModel, Chat, ChatInfo, and preference storage.
+>
+> Related specs: [Architecture](architecture.md) | [API Reference](api.md) | [README](README.md)
+> Related product: [Concept Index](../product/concepts.md)
+
+---
+
+## Table of Contents
+
+1. [Overview](#1-overview)
+2. [ChatModel -- Primary App State](#2-chatmodel)
+3. [ItemsModel -- Per-Chat Message State](#3-itemsmodel)
+4. [ChatTagsModel -- Tag Filtering State](#4-chattagsmodel)
+5. [Chat -- Single Conversation State](#5-chat)
+6. [ChatInfo -- Conversation Metadata](#6-chatinfo)
+7. [State Flow](#7-state-flow)
+8. [Preference Storage](#8-preference-storage)
+
+---
+
+## 1. Overview
+
+The app uses SwiftUI's `ObservableObject` pattern for reactive state management. The state hierarchy is:
+
+```
+ChatModel (singleton -- global app state)
+├── currentUser: User?
+├── users: [UserInfo]
+├── chats: [Chat] (chat list)
+├── chatId: String? (active chat ID)
+├── im: ItemsModel.shared (primary chat items)
+├── secondaryIM: ItemsModel? (secondary chat items, e.g. support scope)
+├── activeCall: Call?
+├── callInvitations: [ChatId: RcvCallInvitation]
+├── deviceToken / savedToken / tokenStatus
+├── notificationMode: NotificationsMode
+├── onboardingStage: OnboardingStage?
+├── migrationState: MigrationToState?
+└── ... (50+ @Published properties)
+
+ItemsModel (singleton + secondary instances -- per-chat message state)
+├── reversedChatItems: [ChatItem] (messages in reverse order)
+├── chatState: ActiveChatState (pagination/split state)
+├── isLoading / showLoadingProgress
+└── preloadState: PreloadState
+
+Chat (per-conversation -- one per entry in chat list)
+├── chatInfo: ChatInfo (type + metadata)
+├── chatItems: [ChatItem] (preview items)
+└── chatStats: ChatStats (unread counts)
+
+ChatTagsModel (singleton -- filter state)
+├── userTags: [ChatTag]
+├── activeFilter: ActiveFilter?
+├── presetTags: [PresetTag: Int]
+└── unreadTags: [Int64: Int]
+```
+
+---
+
+## 2. [ChatModel](../Shared/Model/ChatModel.swift#L337-L1260)
+
+**Class**: `final class ChatModel: ObservableObject`
+**Singleton**: `ChatModel.shared`
+**Source**: [`Shared/Model/ChatModel.swift`](../Shared/Model/ChatModel.swift#L337)
+
+### Key Published Properties
+
+#### App Lifecycle
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `onboardingStage` | `OnboardingStage?` | Current onboarding step | [L331](../Shared/Model/ChatModel.swift#L338) |
+| `chatInitialized` | `Bool` | Whether chat has been initialized | [L340](../Shared/Model/ChatModel.swift#L347) |
+| `chatRunning` | `Bool?` | Whether chat engine is running | [L341](../Shared/Model/ChatModel.swift#L348) |
+| `chatDbChanged` | `Bool` | Whether DB was changed externally | [L342](../Shared/Model/ChatModel.swift#L349) |
+| `chatDbEncrypted` | `Bool?` | Whether DB is encrypted | [L343](../Shared/Model/ChatModel.swift#L350) |
+| `chatDbStatus` | `DBMigrationResult?` | DB migration status | [L344](../Shared/Model/ChatModel.swift#L351) |
+| `ctrlInitInProgress` | `Bool` | Whether controller is initializing | [L345](../Shared/Model/ChatModel.swift#L352) |
+| `migrationState` | `MigrationToState?` | Device migration state | [L390](../Shared/Model/ChatModel.swift#L398) |
+
+#### User State
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `currentUser` | `User?` | Active user profile (triggers theme reapply on change) | [L334](../Shared/Model/ChatModel.swift#L341) |
+| `users` | `[UserInfo]` | All user profiles | [L339](../Shared/Model/ChatModel.swift#L346) |
+| `v3DBMigration` | `V3DBMigrationState` | Legacy DB migration state | [L333](../Shared/Model/ChatModel.swift#L340) |
+
+#### Chat List
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `chats` | `[Chat]` (private set) | All conversations for current user | [L351](../Shared/Model/ChatModel.swift#L358) |
+| `deletedChats` | `Set` | Chat IDs pending deletion animation | [L352](../Shared/Model/ChatModel.swift#L359) |
+
+#### Active Chat
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `chatId` | `String?` | Currently open chat ID | [L354](../Shared/Model/ChatModel.swift#L361) |
+| `chatAgentConnId` | `String?` | Agent connection ID for active chat | [L355](../Shared/Model/ChatModel.swift#L362) |
+| `chatSubStatus` | `SubscriptionStatus?` | Active chat subscription status | [L356](../Shared/Model/ChatModel.swift#L363) |
+| `openAroundItemId` | `ChatItem.ID?` | Item to scroll to when opening | [L357](../Shared/Model/ChatModel.swift#L364) |
+| `chatToTop` | `String?` | Chat to scroll to top | [L358](../Shared/Model/ChatModel.swift#L365) |
+| `groupMembers` | `[GMember]` | Members of active group | [L359](../Shared/Model/ChatModel.swift#L366) |
+| `groupMembersIndexes` | `[Int64: Int]` | Member ID to index mapping | [L360](../Shared/Model/ChatModel.swift#L367) |
+| `membersLoaded` | `Bool` | Whether members have been loaded | [L361](../Shared/Model/ChatModel.swift#L368) |
+| `secondaryIM` | `ItemsModel?` | Secondary items model (e.g. support chat scope) | [L408](../Shared/Model/ChatModel.swift#L416) |
+
+#### Authentication
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `contentViewAccessAuthenticated` | `Bool` | Whether user has passed authentication | [L348](../Shared/Model/ChatModel.swift#L355) |
+| `laRequest` | `LocalAuthRequest?` | Pending authentication request | [L349](../Shared/Model/ChatModel.swift#L356) |
+
+#### Notifications
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `deviceToken` | `DeviceToken?` | Current APNs device token | [L369](../Shared/Model/ChatModel.swift#L376) |
+| `savedToken` | `DeviceToken?` | Previously saved token | [L370](../Shared/Model/ChatModel.swift#L377) |
+| `tokenRegistered` | `Bool` | Whether token is registered with server | [L371](../Shared/Model/ChatModel.swift#L378) |
+| `tokenStatus` | `NtfTknStatus?` | Token registration status | [L373](../Shared/Model/ChatModel.swift#L380) |
+| `notificationMode` | `NotificationsMode` | Current notification mode (.off/.periodic/.instant) | [L374](../Shared/Model/ChatModel.swift#L381) |
+| `notificationServer` | `String?` | Notification server URL | [L375](../Shared/Model/ChatModel.swift#L382) |
+| `notificationPreview` | `NotificationPreviewMode` | What to show in notifications | [L376](../Shared/Model/ChatModel.swift#L383) |
+| `notificationResponse` | `UNNotificationResponse?` | Pending notification action | [L346](../Shared/Model/ChatModel.swift#L353) |
+| `ntfContactRequest` | `NTFContactRequest?` | Pending contact request from notification | [L378](../Shared/Model/ChatModel.swift#L385) |
+| `ntfCallInvitationAction` | `(ChatId, NtfCallAction)?` | Pending call action from notification | [L379](../Shared/Model/ChatModel.swift#L386) |
+
+#### Calls
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `callInvitations` | `[ChatId: RcvCallInvitation]` | Pending incoming call invitations | [L381](../Shared/Model/ChatModel.swift#L388) |
+| `activeCall` | `Call?` | Currently active call | [L382](../Shared/Model/ChatModel.swift#L389) |
+| `callCommand` | `WebRTCCommandProcessor` | WebRTC command queue | [L383](../Shared/Model/ChatModel.swift#L390) |
+| `showCallView` | `Bool` | Whether to show full-screen call UI | [L384](../Shared/Model/ChatModel.swift#L391) |
+| `activeCallViewIsCollapsed` | `Bool` | Whether call view is in PiP mode | [L385](../Shared/Model/ChatModel.swift#L392) |
+
+#### Remote Desktop
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `remoteCtrlSession` | `RemoteCtrlSession?` | Active remote desktop session | [L387](../Shared/Model/ChatModel.swift#L395) |
+
+#### Misc
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `userAddress` | `UserContactLink?` | User's SimpleX address | [L365](../Shared/Model/ChatModel.swift#L372) |
+| `chatItemTTL` | `ChatItemTTL` | Global message TTL | [L366](../Shared/Model/ChatModel.swift#L373) |
+| `appOpenUrl` | `URL?` | URL opened while app active | [L367](../Shared/Model/ChatModel.swift#L374) |
+| `appOpenUrlLater` | `URL?` | URL opened while app inactive | [L368](../Shared/Model/ChatModel.swift#L375) |
+| `showingInvitation` | `ShowingInvitation?` | Currently displayed invitation | [L389](../Shared/Model/ChatModel.swift#L397) |
+| `draft` | `ComposeState?` | Saved compose draft | [L393](../Shared/Model/ChatModel.swift#L401) |
+| `draftChatId` | `String?` | Chat ID for saved draft | [L394](../Shared/Model/ChatModel.swift#L402) |
+| `networkInfo` | `UserNetworkInfo` | Current network type and status | [L395](../Shared/Model/ChatModel.swift#L403) |
+| `conditions` | `ServerOperatorConditions` | Server usage conditions | [L397](../Shared/Model/ChatModel.swift#L405) |
+| `stopPreviousRecPlay` | `URL?` | Currently playing audio source | [L392](../Shared/Model/ChatModel.swift#L400) |
+
+### Non-Published Properties
+
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `messageDelivery` | `[Int64: () -> Void]` | Pending delivery confirmation callbacks | [L399](../Shared/Model/ChatModel.swift#L407) |
+| `filesToDelete` | `Set` | Files queued for deletion | [L401](../Shared/Model/ChatModel.swift#L409) |
+| `im` | `ItemsModel` | Reference to `ItemsModel.shared` | [L405](../Shared/Model/ChatModel.swift#L413) |
+
+### Key Methods
+
+| Method | Description | Line |
+|--------|-------------|------|
+| `getUser(_ userId:)` | Find user by ID | [L427](../Shared/Model/ChatModel.swift#L436) |
+| `updateUser(_ user:)` | Update user in list and current | [L437](../Shared/Model/ChatModel.swift#L447) |
+| `removeUser(_ user:)` | Remove user from list | [L446](../Shared/Model/ChatModel.swift#L457) |
+| `getChat(_ id:)` | Find chat by ID | [L456](../Shared/Model/ChatModel.swift#L468) |
+| `addChat(_ chat:)` | Add chat to list | [L510](../Shared/Model/ChatModel.swift#L523) |
+| `updateChatInfo(_ cInfo:)` | Update chat metadata | [L523](../Shared/Model/ChatModel.swift#L537) |
+| `replaceChat(_ id:, _ chat:)` | Replace chat in list | [L574](../Shared/Model/ChatModel.swift#L589) |
+| `removeChat(_ id:)` | Remove chat from list | [L1180](../Shared/Model/ChatModel.swift#L1198) |
+| `popChat(_ id:, _ ts:)` | Move chat to top of list | [L1157](../Shared/Model/ChatModel.swift#L1174) |
+| `totalUnreadCountForAllUsers()` | Sum unread across all users | [L1058](../Shared/Model/ChatModel.swift#L1074) |
+
+---
+
+## 3. [ItemsModel](../Shared/Model/ChatModel.swift#L74-L174)
+
+**Class**: `class ItemsModel: ObservableObject`
+**Primary singleton**: `ItemsModel.shared`
+**Secondary instances**: Created via `ItemsModel.loadSecondaryChat()` for scope-based views (e.g., group member support chat)
+**Source**: [`Shared/Model/ChatModel.swift`](../Shared/Model/ChatModel.swift#L74)
+
+### Properties
+
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `reversedChatItems` | `[ChatItem]` | Messages in reverse chronological order (newest first) | [L78](../Shared/Model/ChatModel.swift#L80) |
+| `itemAdded` | `Bool` | Flag indicating a new item was added | [L81](../Shared/Model/ChatModel.swift#L83) |
+| `chatState` | `ActiveChatState` | Pagination splits and loaded ranges | [L85](../Shared/Model/ChatModel.swift#L87) |
+| `isLoading` | `Bool` | Whether messages are currently loading | [L89](../Shared/Model/ChatModel.swift#L91) |
+| `showLoadingProgress` | `ChatId?` | Chat ID showing loading spinner | [L90](../Shared/Model/ChatModel.swift#L92) |
+| `preloadState` | `PreloadState` | State for infinite-scroll preloading | [L75](../Shared/Model/ChatModel.swift#L77) |
+| `secondaryIMFilter` | `SecondaryItemsModelFilter?` | Filter for secondary instances | [L74](../Shared/Model/ChatModel.swift#L76) |
+
+### Computed Properties
+
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `lastItemsLoaded` | `Bool` | Whether the oldest messages have been loaded | [L95](../Shared/Model/ChatModel.swift#L97) |
+| `contentTag` | `MsgContentTag?` | Content type filter (if secondary) | [L154](../Shared/Model/ChatModel.swift#L159) |
+| `groupScopeInfo` | `GroupChatScopeInfo?` | Group scope filter (if secondary) | [L162](../Shared/Model/ChatModel.swift#L167) |
+
+### Throttling
+
+`ItemsModel` uses a custom publisher throttle (0.2 seconds) to batch rapid updates to `reversedChatItems` and prevent excessive SwiftUI re-renders:
+
+```swift
+publisher
+ .throttle(for: 0.2, scheduler: DispatchQueue.main, latest: true)
+ .sink { self.objectWillChange.send() }
+ .store(in: &bag)
+```
+
+Direct `@Published` properties (`isLoading`, `showLoadingProgress`) bypass throttling for immediate UI response.
+
+### Key Methods
+
+| Method | Description | Line |
+|--------|-------------|------|
+| `loadOpenChat(_ chatId:)` | Load chat with 250ms navigation delay | [L113](../Shared/Model/ChatModel.swift#L117) |
+| `loadOpenChatNoWait(_ chatId:, _ openAroundItemId:)` | Load chat without delay | [L138](../Shared/Model/ChatModel.swift#L143) |
+| `loadSecondaryChat(_ chatId:, chatFilter:)` | Create secondary ItemsModel instance | [L107](../Shared/Model/ChatModel.swift#L110) |
+
+### [SecondaryItemsModelFilter](../Shared/Model/ChatModel.swift#L58-L70)
+
+Used for secondary chat views (e.g., group member support scope, content type filter):
+
+```swift
+enum SecondaryItemsModelFilter {
+ case groupChatScopeContext(groupScopeInfo: GroupChatScopeInfo)
+ case msgContentTagContext(contentTag: MsgContentTag)
+}
+```
+
+---
+
+## 4. [ChatTagsModel](../Shared/Model/ChatModel.swift#L189-L291)
+
+**Class**: `class ChatTagsModel: ObservableObject`
+**Singleton**: `ChatTagsModel.shared`
+**Source**: [`Shared/Model/ChatModel.swift`](../Shared/Model/ChatModel.swift#L189)
+
+### Properties
+
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `userTags` | `[ChatTag]` | User-defined tags | [L186](../Shared/Model/ChatModel.swift#L192) |
+| `activeFilter` | `ActiveFilter?` | Currently active filter tab | [L187](../Shared/Model/ChatModel.swift#L193) |
+| `presetTags` | `[PresetTag: Int]` | Preset tag counts (groups, contacts, favorites, etc.) | [L188](../Shared/Model/ChatModel.swift#L194) |
+| `unreadTags` | `[Int64: Int]` | Unread count per user tag | [L189](../Shared/Model/ChatModel.swift#L195) |
+
+### [ActiveFilter](../Shared/Views/ChatList/ChatListView.swift#L52)
+
+```swift
+enum ActiveFilter {
+ case presetTag(PresetTag) // .favorites, .contacts, .groups, .business, .groupReports
+ case userTag(ChatTag) // User-defined tag
+ case unread // Unread conversations
+}
+```
+
+---
+
+## 5. [Chat](../Shared/Model/ChatModel.swift#L1311-L1323)
+
+**Class**: `final class Chat: ObservableObject, Identifiable, ChatLike`
+**Source**: [`Shared/Model/ChatModel.swift`](../Shared/Model/ChatModel.swift#L1271)
+
+Represents a single conversation in the chat list. Each `Chat` is an independent observable object.
+
+### Properties
+
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `chatInfo` | `ChatInfo` | Conversation type and metadata | [L1253](../Shared/Model/ChatModel.swift#L1272) |
+| `chatItems` | `[ChatItem]` | Preview items (typically last message) | [L1254](../Shared/Model/ChatModel.swift#L1273) |
+| `chatStats` | `ChatStats` | Unread counts and min unread item ID | [L1255](../Shared/Model/ChatModel.swift#L1274) |
+| `created` | `Date` | Creation timestamp | [L1256](../Shared/Model/ChatModel.swift#L1275) |
+
+### [ChatStats](../SimpleXChat/ChatTypes.swift#L1877-L1899)
+
+```swift
+struct ChatStats: Decodable, Hashable {
+ var unreadCount: Int = 0
+ var unreadMentions: Int = 0
+ var reportsCount: Int = 0
+ var minUnreadItemId: Int64 = 0
+ var unreadChat: Bool = false
+}
+```
+
+### Computed Properties
+
+| Property | Description | Line |
+|----------|-------------|------|
+| `id` | Chat ID from `chatInfo.id` | [L1287](../Shared/Model/ChatModel.swift#L1306) |
+| `viewId` | Unique view identity including creation time | [L1289](../Shared/Model/ChatModel.swift#L1308) |
+| `unreadTag` | Whether chat counts as "unread" based on notification settings | [L1279](../Shared/Model/ChatModel.swift#L1298) |
+| `supportUnreadCount` | Unread count for group support scope | [L1291](../Shared/Model/ChatModel.swift#L1310) |
+
+---
+
+## 6. [ChatInfo](../SimpleXChat/ChatTypes.swift#L1372-L1852)
+
+**Enum**: `public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable`
+**Source**: [`SimpleXChat/ChatTypes.swift`](../SimpleXChat/ChatTypes.swift#L1372)
+
+Represents the type and metadata of a conversation:
+
+```swift
+public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
+ case direct(contact: Contact)
+ case group(groupInfo: GroupInfo, groupChatScope: GroupChatScopeInfo?)
+ case local(noteFolder: NoteFolder)
+ case contactRequest(contactRequest: UserContactRequest)
+ case contactConnection(contactConnection: PendingContactConnection)
+ case invalidJSON(json: Data?)
+}
+```
+
+### Cases
+
+| Case | Associated Value | Description |
+|------|-----------------|-------------|
+| `.direct` | `Contact` | One-to-one conversation |
+| `.group` | `GroupInfo, GroupChatScopeInfo?` | Group conversation (optional scope for member support threads) |
+| `.local` | `NoteFolder` | Local notes (self-chat) |
+| `.contactRequest` | `UserContactRequest` | Incoming contact request |
+| `.contactConnection` | `PendingContactConnection` | Pending connection |
+| `.invalidJSON` | `Data?` | Undecodable chat data |
+
+### Key Computed Properties on ChatInfo
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `chatType` | `ChatType` | `.direct`, `.group`, `.local`, `.contactRequest`, `.contactConnection` |
+| `id` | `ChatId` | Prefixed ID (e.g., `"@1"` for direct, `"#5"` for group) |
+| `displayName` | `String` | Contact/group name |
+| `image` | `String?` | Profile image (base64) |
+| `chatSettings` | `ChatSettings?` | Notification/favorite settings |
+| `chatTags` | `[Int64]?` | Assigned tag IDs |
+
+---
+
+## 7. State Flow
+
+### App Start
+```
+SimpleXApp.init()
+ → haskell_init()
+ → initChatAndMigrate()
+ → chat_migrate_init_key() -- creates/opens DB
+ → startChat(mainApp: true) -- starts core
+ → apiGetChats(userId) -- populates ChatModel.chats
+ → UI renders ChatListView
+```
+
+### Opening a Chat
+```
+User taps chat in ChatListView
+ → ItemsModel.loadOpenChat(chatId)
+ → 250ms delay for navigation animation
+ → ChatModel.chatId = chatId
+ → loadChat(chatId:, im:)
+ → apiGetChat(chatId, pagination: .last(count: 50))
+ → ItemsModel.reversedChatItems = [ChatItem]
+ → ChatView renders messages
+```
+
+### Receiving a Message (Event)
+```
+Haskell core generates ChatEvent.newChatItems
+ → Event loop calls chat_recv_msg_wait
+ → Decoded as ChatEvent.newChatItems(user, chatItems)
+ → ChatModel updates:
+ 1. Insert new Chat items into ChatModel.chats (preview)
+ 2. If chat is open: insert into ItemsModel.reversedChatItems
+ 3. Update ChatStats (unread counts)
+ 4. Update ChatTagsModel (tag unread counts)
+ → SwiftUI re-renders affected views via @Published observation
+```
+
+### Sending a Message
+```
+User taps send in ComposeView
+ → apiSendMessages(type, id, scope, live, ttl, composedMessages)
+ → Haskell processes, returns ChatResponse1.newChatItems
+ → ChatModel.chats updated with new preview
+ → ItemsModel.reversedChatItems gets new item
+ → ChatView scrolls to bottom, shows sent message
+```
+
+---
+
+## 8. Preference Storage
+
+### UserDefaults (via @AppStorage)
+
+App-level UI settings stored in `UserDefaults.standard`:
+
+| Key Constant | Type | Description |
+|--------------|------|-------------|
+| `DEFAULT_PERFORM_LA` | `Bool` | Enable local authentication |
+| `DEFAULT_PRIVACY_PROTECT_SCREEN` | `Bool` | Hide screen in app switcher |
+| `DEFAULT_SHOW_LA_NOTICE` | `Bool` | Show LA setup notice |
+| `DEFAULT_NOTIFICATION_ALERT_SHOWN` | `Bool` | Notification permission alert shown |
+| `DEFAULT_CALL_KIT_CALLS_IN_RECENTS` | `Bool` | Show CallKit calls in recents |
+
+### GroupDefaults
+
+Settings shared between main app and extensions (NSE, SE) via app group `UserDefaults`:
+
+| Key | Description |
+|-----|-------------|
+| `appStateGroupDefault` | Current app state (.active/.suspended/.stopped) |
+| `dbContainerGroupDefault` | Database container location (.group/.documents) |
+| `ntfPreviewModeGroupDefault` | Notification preview mode |
+| `storeDBPassphraseGroupDefault` | Whether to store DB passphrase |
+| `callKitEnabledGroupDefault` | Whether CallKit is enabled |
+| `onboardingStageDefault` | Current onboarding stage |
+| `currentThemeDefault` | Current theme name |
+| `systemDarkThemeDefault` | Dark mode theme name |
+| `themeOverridesDefault` | Custom theme overrides |
+| `currentThemeIdsDefault` | Active theme override IDs |
+
+### Keychain (KeyChain wrapper)
+
+Sensitive data stored in iOS Keychain:
+
+| Key | Description |
+|-----|-------------|
+| `kcDatabasePassword` | SQLite database encryption key |
+| `kcAppPassword` | App lock password |
+| `kcSelfDestructPassword` | Self-destruct trigger password |
+
+### Haskell DB (via apiSaveSettings / apiGetSettings)
+
+Chat-level preferences stored in the SQLite database (managed by Haskell core):
+
+- Per-contact preferences (timed messages, voice, calls, etc.)
+- Per-group preferences
+- Per-user notification settings
+- Network configuration
+- Server lists
+
+---
+
+## Source Files
+
+| File | Path |
+|------|------|
+| ChatModel, ItemsModel, Chat, ChatTagsModel | [`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) |
+| Preference defaults | [`Shared/Model/ChatModel.swift`](../Shared/Model/ChatModel.swift), [`SimpleXChat/FileUtils.swift`](../SimpleXChat/FileUtils.swift) |
diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings
index 2700711773..d4b4dfd949 100644
--- a/apps/ios/th.lproj/Localizable.strings
+++ b/apps/ios/th.lproj/Localizable.strings
@@ -910,7 +910,8 @@ swipe action */
/* No comment provided by engineer. */
"Delete message?" = "ลบข้อความ?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "ลบข้อความ";
/* No comment provided by engineer. */
@@ -1815,7 +1816,7 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "บทบาทของสมาชิกจะถูกเปลี่ยนเป็น \"%@\" สมาชิกจะได้รับคำเชิญใหม่";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "สมาชิกจะถูกลบออกจากกลุ่ม - ไม่สามารถยกเลิกได้!";
/* No comment provided by engineer. */
@@ -2332,13 +2333,13 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "เซิร์ฟเวอร์รีเลย์ปกป้องที่อยู่ IP ของคุณ แต่สามารถสังเกตระยะเวลาของการโทรได้";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "ลบ";
/* No comment provided by engineer. */
"Remove member" = "ลบสมาชิกออก";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "ลบสมาชิกออก?";
/* No comment provided by engineer. */
diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings
index 9acf2cc425..5cccb67170 100644
--- a/apps/ios/tr.lproj/Localizable.strings
+++ b/apps/ios/tr.lproj/Localizable.strings
@@ -1733,7 +1733,8 @@ swipe action */
/* No comment provided by engineer. */
"Delete message?" = "Mesaj silinsin mi?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "Mesajları sil";
/* No comment provided by engineer. */
@@ -3293,10 +3294,10 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "Üye rolü \"%@\" olarak değiştirilecektir. Ve üye yeni bir davetiye alacaktır.";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from chat - this cannot be undone!" = "Üye sohbetten kaldırılacak - bu geri alınamaz!";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "Üye gruptan çıkarılacaktır - bu geri alınamaz!";
/* alert message */
@@ -4362,7 +4363,7 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "Yönlendirici sunucu IP adresinizi korur, ancak aramanın süresini gözlemleyebilir.";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "Sil";
/* No comment provided by engineer. */
@@ -4377,7 +4378,7 @@ swipe action */
/* No comment provided by engineer. */
"Remove member" = "Kişiyi sil";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "Kişi silinsin mi?";
/* No comment provided by engineer. */
diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings
index fe8cfe22a0..305e64fbcf 100644
--- a/apps/ios/uk.lproj/Localizable.strings
+++ b/apps/ios/uk.lproj/Localizable.strings
@@ -1718,7 +1718,8 @@ swipe action */
/* No comment provided by engineer. */
"Delete message?" = "Видалити повідомлення?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "Видалити повідомлення";
/* No comment provided by engineer. */
@@ -3263,10 +3264,10 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "Роль учасника буде змінено на \"%@\". Учасник отримає нове запрошення.";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from chat - this cannot be undone!" = "Учасника буде видалено з чату – це неможливо скасувати!";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "Учасник буде видалений з групи - це неможливо скасувати!";
/* alert message */
@@ -4317,7 +4318,7 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "Сервер ретрансляції захищає вашу IP-адресу, але він може спостерігати за тривалістю дзвінка.";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "Видалити";
/* No comment provided by engineer. */
@@ -4329,7 +4330,7 @@ swipe action */
/* No comment provided by engineer. */
"Remove member" = "Видалити учасника";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "Видалити учасника?";
/* No comment provided by engineer. */
diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings
index 24d153afd5..ff80559fb1 100644
--- a/apps/ios/zh-Hans.lproj/Localizable.strings
+++ b/apps/ios/zh-Hans.lproj/Localizable.strings
@@ -346,12 +346,21 @@ alert action
swipe action */
"Accept" = "接受";
+/* alert action */
+"Accept as member" = "接受为成员";
+
+/* alert action */
+"Accept as observer" = "接受为观察员";
+
/* No comment provided by engineer. */
"Accept conditions" = "接受条款";
/* No comment provided by engineer. */
"Accept connection request?" = "接受联系人?";
+/* alert title */
+"Accept contact request" = "接受联络请求";
+
/* notification body */
"Accept contact request from %@?" = "接受来自 %@ 的联系人请求?";
@@ -359,12 +368,21 @@ swipe action */
swipe action */
"Accept incognito" = "接受隐身聊天";
+/* alert title */
+"Accept member" = "接受成员";
+
/* call status */
"accepted call" = "已接受通话";
/* No comment provided by engineer. */
"Accepted conditions" = "已接受的条款";
+/* chat list item title */
+"accepted invitation" = "已接受邀请";
+
+/* rcv group event chat item */
+"accepted you" = "接受了你";
+
/* No comment provided by engineer. */
"Acknowledged" = "确认";
@@ -386,6 +404,9 @@ swipe action */
/* No comment provided by engineer. */
"Add list" = "添加列表";
+/* placeholder for sending contact request */
+"Add message" = "添加信息";
+
/* No comment provided by engineer. */
"Add profile" = "添加个人资料";
@@ -461,6 +482,9 @@ swipe action */
/* chat item text */
"agreeing encryption…" = "同意加密…";
+/* member criteria value */
+"all" = "全部";
+
/* No comment provided by engineer. */
"All" = "全部";
@@ -530,6 +554,9 @@ swipe action */
/* No comment provided by engineer. */
"Allow downgrade" = "允许降级";
+/* No comment provided by engineer. */
+"Allow files and media only if your contact allows them." = "只有你的联系人允许的情况下才允许文件和媒体。";
+
/* No comment provided by engineer. */
"Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "仅有您的联系人许可后才允许不可撤回消息移除";
@@ -581,6 +608,9 @@ swipe action */
/* No comment provided by engineer. */
"Allow your contacts to send disappearing messages." = "允许您的联系人发送限时消息。";
+/* No comment provided by engineer. */
+"Allow your contacts to send files and media." = "允许你的联系人发送文件和媒体。";
+
/* No comment provided by engineer. */
"Allow your contacts to send voice messages." = "允许您的联系人发送语音消息。";
@@ -683,6 +713,9 @@ swipe action */
/* No comment provided by engineer. */
"Archived contacts" = "已存档的联系人";
+/* No comment provided by engineer. */
+"archived report" = "已存档的举报";
+
/* No comment provided by engineer. */
"Archiving database" = "正在存档数据库";
@@ -782,6 +815,12 @@ swipe action */
/* No comment provided by engineer. */
"Better user experience" = "更佳的使用体验";
+/* No comment provided by engineer. */
+"Bio" = "自我介绍";
+
+/* alert title */
+"Bio too large" = "自我介绍过大";
+
/* No comment provided by engineer. */
"Black" = "黑色";
@@ -825,6 +864,9 @@ marked deleted chat item preview text */
/* No comment provided by engineer. */
"bold" = "加粗";
+/* No comment provided by engineer. */
+"Bot" = "机器人";
+
/* No comment provided by engineer. */
"Both you and your contact can add message reactions." = "您和您的联系人都可以添加消息回应。";
@@ -837,6 +879,9 @@ marked deleted chat item preview text */
/* No comment provided by engineer. */
"Both you and your contact can send disappearing messages." = "您和您的联系人都可以发送限时消息。";
+/* No comment provided by engineer. */
+"Both you and your contact can send files and media." = "你和你的联系人都可发送文件和媒体。";
+
/* No comment provided by engineer. */
"Both you and your contact can send voice messages." = "您和您的联系人都可以发送语音消息。";
@@ -849,6 +894,9 @@ marked deleted chat item preview text */
/* No comment provided by engineer. */
"Business chats" = "企业聊天";
+/* No comment provided by engineer. */
+"Business connection" = "企业连接";
+
/* No comment provided by engineer. */
"Businesses" = "企业";
@@ -888,6 +936,9 @@ marked deleted chat item preview text */
/* No comment provided by engineer. */
"Can't call member" = "无法呼叫成员";
+/* alert title */
+"Can't change profile" = "无法更改个人资料";
+
/* No comment provided by engineer. */
"Can't invite contact!" = "无法邀请联系人!";
@@ -897,6 +948,9 @@ marked deleted chat item preview text */
/* No comment provided by engineer. */
"Can't message member" = "无法向成员发送消息";
+/* No comment provided by engineer. */
+"can't send messages" = "无法发送消息";
+
/* alert action
alert button
new chat action */
@@ -1035,9 +1089,21 @@ set passcode view */
/* No comment provided by engineer. */
"Chat will be deleted for you - this cannot be undone!" = "将为你删除聊天 - 此操作无法撤销!";
+/* chat toolbar */
+"Chat with admins" = "和管理员聊天";
+
+/* No comment provided by engineer. */
+"Chat with member" = "和成员聊天";
+
+/* No comment provided by engineer. */
+"Chat with members before they join." = "在成员加入前和这些人聊天";
+
/* No comment provided by engineer. */
"Chats" = "聊天";
+/* No comment provided by engineer. */
+"Chats with members" = "和成员聊天";
+
/* No comment provided by engineer. */
"Check messages every 20 min." = "每 20 分钟检查消息。";
@@ -1179,6 +1245,9 @@ set passcode view */
/* No comment provided by engineer. */
"Connect automatically" = "自动连接";
+/* No comment provided by engineer. */
+"Connect faster! 🚀" = "更快地连接!🚀";
+
/* No comment provided by engineer. */
"Connect to desktop" = "连接到桌面";
@@ -1317,9 +1386,15 @@ set passcode view */
/* No comment provided by engineer. */
"Contact already exists" = "联系人已存在";
+/* No comment provided by engineer. */
+"contact deleted" = "删除了联系人";
+
/* No comment provided by engineer. */
"Contact deleted!" = "联系人已删除!";
+/* No comment provided by engineer. */
+"contact disabled" = "禁用了联系人";
+
/* No comment provided by engineer. */
"contact has e2e encryption" = "联系人具有端到端加密";
@@ -1338,9 +1413,18 @@ set passcode view */
/* No comment provided by engineer. */
"Contact name" = "联系人姓名";
+/* No comment provided by engineer. */
+"contact not ready" = "联系人未就绪";
+
/* No comment provided by engineer. */
"Contact preferences" = "联系人偏好设置";
+/* No comment provided by engineer. */
+"Contact requests from groups" = "来自群的联络请求";
+
+/* No comment provided by engineer. */
+"contact should accept…" = "联系人应当接受…";
+
/* No comment provided by engineer. */
"Contact will be deleted - this cannot be undone!" = "联系人将被删除-这是无法撤消的!";
@@ -1410,6 +1494,9 @@ set passcode view */
/* No comment provided by engineer. */
"Create SimpleX address" = "创建 SimpleX 地址";
+/* No comment provided by engineer. */
+"Create your address" = "创建地址";
+
/* No comment provided by engineer. */
"Create your profile" = "创建您的资料";
@@ -1583,6 +1670,9 @@ swipe action */
/* No comment provided by engineer. */
"Delete chat profile?" = "删除聊天资料?";
+/* alert title */
+"Delete chat with member?" = "删除和成员的聊天吗?";
+
/* No comment provided by engineer. */
"Delete chat?" = "删除聊天?";
@@ -1640,7 +1730,8 @@ swipe action */
/* No comment provided by engineer. */
"Delete message?" = "删除消息吗?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "删除消息";
/* No comment provided by engineer. */
@@ -1709,9 +1800,15 @@ swipe action */
/* No comment provided by engineer. */
"Delivery receipts!" = "送达回执!";
+/* No comment provided by engineer. */
+"Deprecated options" = "已废弃的选项";
+
/* No comment provided by engineer. */
"Description" = "描述";
+/* alert title */
+"Description too large" = "描述过大";
+
/* No comment provided by engineer. */
"Desktop address" = "桌面地址";
@@ -1914,6 +2011,9 @@ chat item action */
/* No comment provided by engineer. */
"Edit group profile" = "编辑群组资料";
+/* No comment provided by engineer. */
+"Empty message!" = "空消息!";
+
/* No comment provided by engineer. */
"Enable" = "启用";
@@ -1926,6 +2026,9 @@ chat item action */
/* No comment provided by engineer. */
"Enable camera access" = "启用相机访问";
+/* No comment provided by engineer. */
+"Enable disappearing messages by default." = "默认启用定时消失消息。";
+
/* No comment provided by engineer. */
"Enable Flux in Network & servers settings for better metadata privacy." = "在“网络&服务器”设置中启用 Flux,更好地保护元数据隐私。";
@@ -2097,15 +2200,24 @@ chat item action */
/* No comment provided by engineer. */
"Error accepting contact request" = "接受联系人请求错误";
+/* alert title */
+"Error accepting member" = "接受成员出错";
+
/* No comment provided by engineer. */
"Error adding member(s)" = "添加成员错误";
/* alert title */
"Error adding server" = "添加服务器出错";
+/* No comment provided by engineer. */
+"Error adding short link" = "添加短链接出错";
+
/* No comment provided by engineer. */
"Error changing address" = "更改地址错误";
+/* alert title */
+"Error changing chat profile" = "更改聊天资料出错";
+
/* No comment provided by engineer. */
"Error changing connection profile" = "更改连接资料出错";
@@ -2118,6 +2230,9 @@ chat item action */
/* No comment provided by engineer. */
"Error changing to incognito!" = "切换至隐身聊天出错!";
+/* No comment provided by engineer. */
+"Error checking token status" = "查询token状态出错";
+
/* alert message */
"Error connecting to forwarding server %@. Please try later." = "连接到转发服务器 %@ 时出错。请稍后尝试。";
@@ -2148,6 +2263,9 @@ chat item action */
/* No comment provided by engineer. */
"Error decrypting file" = "解密文件时出错";
+/* alert title */
+"Error deleting chat" = "删除聊天出错";
+
/* alert title */
"Error deleting chat database" = "删除聊天数据库错误";
@@ -2202,6 +2320,9 @@ chat item action */
/* No comment provided by engineer. */
"Error opening chat" = "打开聊天时出错";
+/* No comment provided by engineer. */
+"Error opening group" = "打开群时出错";
+
/* alert title */
"Error receiving file" = "接收文件错误";
@@ -2214,6 +2335,9 @@ chat item action */
/* alert title */
"Error registering for notifications" = "注册消息推送出错";
+/* alert title */
+"Error rejecting contact request" = "拒绝联络请求出错";
+
/* alert title */
"Error removing member" = "删除成员错误";
@@ -2259,6 +2383,9 @@ chat item action */
/* No comment provided by engineer. */
"Error sending message" = "发送消息错误";
+/* No comment provided by engineer. */
+"Error setting auto-accept" = "设置自动接受出错";
+
/* No comment provided by engineer. */
"Error setting delivery receipts!" = "设置送达回执出错!";
@@ -2309,6 +2436,9 @@ file error text
snd error text */
"Error: %@" = "错误: %@";
+/* server test error */
+"Error: %@." = "错误:%@。";
+
/* No comment provided by engineer. */
"Error: no database file" = "错误:没有数据库文件";
@@ -2417,6 +2547,9 @@ snd error text */
/* chat feature */
"Files and media" = "文件和媒体";
+/* No comment provided by engineer. */
+"Files and media are prohibited in this chat." = "此聊天禁止文件和媒体。";
+
/* No comment provided by engineer. */
"Files and media are prohibited." = "此群组中禁止文件和媒体。";
@@ -2441,6 +2574,15 @@ snd error text */
/* No comment provided by engineer. */
"Find chats faster" = "更快地查找聊天记录";
+/* No comment provided by engineer. */
+"Fingerprint in destination server address does not match certificate: %@." = "目的地服务器的指纹与证书不符:%@。";
+
+/* No comment provided by engineer. */
+"Fingerprint in forwarding server address does not match certificate: %@." = "转发服务器的指纹与证书不符:%@。";
+
+/* No comment provided by engineer. */
+"Fingerprint in server address does not match certificate: %@." = "服务器的指纹与证书不符:%@。";
+
/* server test error */
"Fingerprint in server address does not match certificate." = "服务器地址中的证书指纹可能不正确";
@@ -2561,6 +2703,9 @@ snd error text */
/* message preview */
"Good morning!" = "早上好!";
+/* shown on group welcome message */
+"group" = "群";
+
/* No comment provided by engineer. */
"Group" = "群组";
@@ -2591,6 +2736,9 @@ snd error text */
/* No comment provided by engineer. */
"Group invitation is no longer valid, it was removed by sender." = "群组邀请不再有效,已被发件人删除。";
+/* No comment provided by engineer. */
+"group is deleted" = "群被删除了";
+
/* No comment provided by engineer. */
"Group link" = "群组链接";
@@ -2615,6 +2763,9 @@ snd error text */
/* snd group event chat item */
"group profile updated" = "群组资料已更新";
+/* alert message */
+"Group profile was changed. If you save it, the updated profile will be sent to group members." = "群资料已修改。如果你进行保存,修改后的群资料将发送给其他群成员。";
+
/* No comment provided by engineer. */
"Group welcome message" = "群欢迎词";
@@ -2990,6 +3141,9 @@ snd error text */
/* alert title */
"Keep unused invitation?" = "保留未使用的邀请吗?";
+/* No comment provided by engineer. */
+"Keep your chats clean" = "保持聊天洁净";
+
/* No comment provided by engineer. */
"Keep your connections" = "保持连接";
@@ -3023,6 +3177,9 @@ snd error text */
/* rcv group event chat item */
"left" = "已离开";
+/* No comment provided by engineer. */
+"Less traffic on mobile networks." = "消耗更少的移动网络数据。";
+
/* email subject */
"Let's talk in SimpleX Chat" = "让我们一起在 SimpleX Chat 里聊天";
@@ -3059,6 +3216,9 @@ snd error text */
/* No comment provided by engineer. */
"Live messages" = "实时消息";
+/* in progress text */
+"Loading profile…" = "正加载个人资料…";
+
/* No comment provided by engineer. */
"Local name" = "本地名称";
@@ -3110,15 +3270,27 @@ snd error text */
/* No comment provided by engineer. */
"Member" = "成员";
+/* past/unknown group member */
+"Member %@" = "成员 %@";
+
/* profile update event chat item */
"member %@ changed to %@" = "成员 %1$@ 已更改为 %2$@";
+/* No comment provided by engineer. */
+"Member admission" = "成员准入";
+
/* rcv group event chat item */
"member connected" = "已连接";
+/* No comment provided by engineer. */
+"member has old version" = "成员有旧版本";
+
/* item status text */
"Member inactive" = "成员不活跃";
+/* No comment provided by engineer. */
+"Member is deleted - can't accept request" = "成员被删除——无法接受请求";
+
/* chat feature */
"Member reports" = "成员举报";
@@ -3131,12 +3303,15 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "成员角色将更改为 \"%@\"。该成员将收到一份新的邀请。";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from chat - this cannot be undone!" = "将从聊天中删除成员 - 此操作无法撤销!";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "成员将被移出群组——此操作无法撤消!";
+/* alert message */
+"Member will join the group, accept member?" = "成员将加入本群,接受成员吗?";
+
/* No comment provided by engineer. */
"Members can add message reactions." = "群组成员可以添加信息回应。";
@@ -3185,6 +3360,9 @@ snd error text */
/* item status text */
"Message forwarded" = "消息已转发";
+/* No comment provided by engineer. */
+"Message instantly once you tap Connect." = "轻按连接后即刻发消息。";
+
/* item status description */
"Message may be delivered later if member becomes active." = "如果 member 变为活动状态,则稍后可能会发送消息。";
@@ -3233,6 +3411,9 @@ snd error text */
/* No comment provided by engineer. */
"Messages & files" = "消息";
+/* No comment provided by engineer. */
+"Messages are protected by **end-to-end encryption**." = "消息已通过**端到端加密**保护。";
+
/* No comment provided by engineer. */
"Messages from %@ will be shown!" = "将显示来自 %@ 的消息!";
@@ -3311,6 +3492,9 @@ snd error text */
/* marked deleted chat item preview text */
"moderated by %@" = "由 %@ 审核";
+/* member role */
+"moderator" = "协管";
+
/* time unit */
"months" = "月";
@@ -3395,6 +3579,9 @@ snd error text */
/* notification */
"New events" = "新事件";
+/* No comment provided by engineer. */
+"New group role: Moderator" = "新的群角色:协管";
+
/* No comment provided by engineer. */
"New in %@" = "%@ 的新内容";
@@ -3404,6 +3591,9 @@ snd error text */
/* No comment provided by engineer. */
"New member role" = "新成员角色";
+/* rcv group event chat item */
+"New member wants to join the group." = "新成员要加入本群。";
+
/* notification */
"new message" = "新消息";
@@ -3443,6 +3633,9 @@ snd error text */
/* No comment provided by engineer. */
"No chats in list %@" = "列表 %@ 中无聊天";
+/* No comment provided by engineer. */
+"No chats with members" = "没有和成员的聊天";
+
/* No comment provided by engineer. */
"No contacts selected" = "未选择联系人";
@@ -3494,6 +3687,9 @@ snd error text */
/* No comment provided by engineer. */
"No permission to record voice message" = "没有录制语音消息的权限";
+/* alert title */
+"No private routing session" = "无私密路由会话";
+
/* No comment provided by engineer. */
"No push server" = "本地";
@@ -3512,6 +3708,9 @@ snd error text */
/* servers error */
"No servers to send files." = "无文件发送服务器。";
+/* No comment provided by engineer. */
+"no subscription" = "无订阅";
+
/* copied message info in history */
"no text" = "无文本";
@@ -3527,6 +3726,9 @@ snd error text */
/* No comment provided by engineer. */
"Not compatible!" = "不兼容!";
+/* No comment provided by engineer. */
+"not synchronized" = "未同步";
+
/* No comment provided by engineer. */
"Notes" = "附注";
@@ -3634,6 +3836,9 @@ new chat action */
/* No comment provided by engineer. */
"Only you can send disappearing messages." = "只有您可以发送限时消息。";
+/* No comment provided by engineer. */
+"Only you can send files and media." = "只有你可以发送文件和媒体。";
+
/* No comment provided by engineer. */
"Only you can send voice messages." = "只有您可以发送语音消息。";
@@ -3649,6 +3854,9 @@ new chat action */
/* No comment provided by engineer. */
"Only your contact can send disappearing messages." = "只有您的联系人才可以发送限时消息。";
+/* No comment provided by engineer. */
+"Only your contact can send files and media." = "只有你的联系人可以发送文件和媒体。";
+
/* No comment provided by engineer. */
"Only your contact can send voice messages." = "只有您的联系人可以发送语音消息。";
@@ -3664,18 +3872,45 @@ new chat action */
/* authentication reason */
"Open chat console" = "打开聊天控制台";
+/* alert action */
+"Open clean link" = "打开干净链接";
+
/* No comment provided by engineer. */
"Open conditions" = "打开条款";
+/* alert action */
+"Open full link" = "打开完整链接";
+
/* new chat action */
"Open group" = "打开群";
+/* alert title */
+"Open link?" = "打开链接?";
+
/* authentication reason */
"Open migration to another device" = "打开迁移到另一台设备";
+/* new chat action */
+"Open new chat" = "打开新聊天";
+
+/* new chat action */
+"Open new group" = "打开新群";
+
/* No comment provided by engineer. */
"Open Settings" = "打开设置";
+/* No comment provided by engineer. */
+"Open to accept" = "打开以接受";
+
+/* No comment provided by engineer. */
+"Open to connect" = "打开以连接";
+
+/* No comment provided by engineer. */
+"Open to join" = "打开以加入";
+
+/* No comment provided by engineer. */
+"Open to use bot" = "打开来使用机器人";
+
/* No comment provided by engineer. */
"Opening app…" = "正在打开应用程序…";
@@ -3715,6 +3950,9 @@ new chat action */
/* No comment provided by engineer. */
"other errors" = "其他错误";
+/* alert message */
+"Other file errors:\n%@" = "其他文件错误:\n%@";
+
/* member role */
"owner" = "群主";
@@ -3760,6 +3998,12 @@ new chat action */
/* No comment provided by engineer. */
"Pending" = "待定";
+/* No comment provided by engineer. */
+"pending approval" = "待批准";
+
+/* No comment provided by engineer. */
+"pending review" = "待审核";
+
/* No comment provided by engineer. */
"Periodic" = "定期";
@@ -3826,15 +4070,33 @@ new chat action */
/* No comment provided by engineer. */
"Please store passphrase securely, you will NOT be able to change it if you lose it." = "请安全地保存密码,如果您丢失了密码,您将无法更改它。";
+/* token info */
+"Please try to disable and re-enable notfications." = "请尝试禁用并重新启用通知。";
+
+/* snd group event chat item */
+"Please wait for group moderators to review your request to join the group." = "请等待群的协管审核你加入该群的请求。";
+
+/* token info */
+"Please wait for token activation to complete." = "请等待token激活完成。";
+
+/* token info */
+"Please wait for token to be registered." = "请等待token注册完成。";
+
/* No comment provided by engineer. */
"Polish interface" = "波兰语界面";
+/* No comment provided by engineer. */
+"Port" = "端口";
+
/* No comment provided by engineer. */
"Preserve the last message draft, with attachments." = "保留最后的消息草稿及其附件。";
/* No comment provided by engineer. */
"Preset server address" = "预设服务器地址";
+/* No comment provided by engineer. */
+"Preset servers" = "预设服务器";
+
/* No comment provided by engineer. */
"Preview" = "预览";
@@ -3844,6 +4106,9 @@ new chat action */
/* No comment provided by engineer. */
"Privacy & security" = "隐私和安全";
+/* No comment provided by engineer. */
+"Privacy for your customers." = "客户隐私。";
+
/* No comment provided by engineer. */
"Privacy policy and conditions of use." = "隐私政策和使用条款。";
@@ -3856,6 +4121,9 @@ new chat action */
/* No comment provided by engineer. */
"Private filenames" = "私密文件名";
+/* No comment provided by engineer. */
+"Private media file names." = "私密媒体文件名。";
+
/* No comment provided by engineer. */
"Private message routing" = "私有消息路由";
@@ -3871,6 +4139,9 @@ new chat action */
/* alert title */
"Private routing error" = "专用路由错误";
+/* alert title */
+"Private routing timeout" = "私密路由超时";
+
/* No comment provided by engineer. */
"Profile and server connections" = "资料和服务器连接";
@@ -3901,6 +4172,9 @@ new chat action */
/* No comment provided by engineer. */
"Prohibit messages reactions." = "禁止消息回应。";
+/* No comment provided by engineer. */
+"Prohibit reporting messages to moderators." = "禁止向 协管 举报消息。";
+
/* No comment provided by engineer. */
"Prohibit sending direct messages to members." = "禁止向成员发送私信。";
@@ -3928,6 +4202,9 @@ new chat action */
/* No comment provided by engineer. */
"Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "保护您的 IP 地址免受联系人选择的消息中继的攻击。\n在*网络和服务器*设置中启用。";
+/* No comment provided by engineer. */
+"Protocol background timeout" = "协议后台超时";
+
/* No comment provided by engineer. */
"Protocol timeout" = "协议超时";
@@ -3940,6 +4217,9 @@ new chat action */
/* No comment provided by engineer. */
"Proxied servers" = "代理服务器";
+/* No comment provided by engineer. */
+"Proxy requires password" = "代理需要密码";
+
/* No comment provided by engineer. */
"Push notifications" = "推送通知";
@@ -4057,6 +4337,12 @@ new chat action */
/* No comment provided by engineer. */
"Reduced battery usage" = "减少电池使用量";
+/* No comment provided by engineer. */
+"Register" = "注册";
+
+/* token status text */
+"Registered" = "已注册";
+
/* alert action
reject incoming call via notification
swipe action */
@@ -4068,6 +4354,12 @@ swipe action */
/* alert title */
"Reject contact request" = "拒绝联系人请求";
+/* alert title */
+"Reject member?" = "拒绝成员?";
+
+/* No comment provided by engineer. */
+"rejected" = "被拒绝";
+
/* call status */
"rejected call" = "拒接来电";
@@ -4077,16 +4369,22 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "中继服务器保护您的 IP 地址,但它可以观察通话的持续时间。";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "移除";
+/* No comment provided by engineer. */
+"Remove archive?" = "删除存档?";
+
/* No comment provided by engineer. */
"Remove image" = "移除图片";
/* No comment provided by engineer. */
-"Remove member" = "删除成员";
+"Remove link tracking" = "删除链接跟踪";
/* No comment provided by engineer. */
+"Remove member" = "删除成员";
+
+/* alert title */
"Remove member?" = "删除成员吗?";
/* No comment provided by engineer. */
@@ -4101,12 +4399,18 @@ swipe action */
/* profile update event chat item */
"removed contact address" = "删除了联系地址";
+/* No comment provided by engineer. */
+"removed from group" = "从群被删除了";
+
/* profile update event chat item */
"removed profile picture" = "删除了资料图片";
/* rcv group event chat item */
"removed you" = "已将您移除";
+/* No comment provided by engineer. */
+"Removes messages and blocks members." = "删除消息并封禁成员。";
+
/* No comment provided by engineer. */
"Renegotiate" = "重新协商";
@@ -4128,6 +4432,54 @@ swipe action */
/* chat item action */
"Reply" = "回复";
+/* chat item action */
+"Report" = "举报";
+
+/* report reason */
+"Report content: only group moderators will see it." = "举报内容:仅协管会看到。";
+
+/* report reason */
+"Report member profile: only group moderators will see it." = "举报成员个人资料:仅协管会看到。";
+
+/* report reason */
+"Report other: only group moderators will see it." = "举报其他:仅协管会看到。";
+
+/* No comment provided by engineer. */
+"Report reason?" = "举报理由?";
+
+/* alert title */
+"Report sent to moderators" = "举报已发送至 协管";
+
+/* report reason */
+"Report spam: only group moderators will see it." = "举报垃圾信息:仅协管会看到。";
+
+/* report reason */
+"Report violation: only group moderators will see it." = "举报违规:仅协管会看到。";
+
+/* report in notification */
+"Report: %@" = "举报: %@";
+
+/* No comment provided by engineer. */
+"Reporting messages to moderators is prohibited." = "向协管举报消息已被禁止。";
+
+/* No comment provided by engineer. */
+"Reports" = "举报";
+
+/* No comment provided by engineer. */
+"request is sent" = "发送了请求";
+
+/* No comment provided by engineer. */
+"request to join rejected" = "加入请求被拒绝";
+
+/* rcv group event chat item */
+"requested connection" = "已请求连接";
+
+/* rcv direct event chat item */
+"requested connection from group %@" = "来自群组%@的已请求连接";
+
+/* chat list item title */
+"requested to connect" = "被请求连接";
+
/* No comment provided by engineer. */
"Required" = "必须";
@@ -4179,9 +4531,21 @@ swipe action */
/* chat item action */
"Reveal" = "揭示";
+/* No comment provided by engineer. */
+"review" = "审核";
+
/* No comment provided by engineer. */
"Review conditions" = "审阅条款";
+/* No comment provided by engineer. */
+"Review group members" = "审核群成员";
+
+/* admission stage */
+"Review members" = "审核成员";
+
+/* No comment provided by engineer. */
+"reviewed by admins" = "由管理员审核";
+
/* No comment provided by engineer. */
"Revoke" = "吊销";
@@ -4210,6 +4574,12 @@ chat item action */
/* alert button */
"Save (and notify contacts)" = "保存(并通知联系人)";
+/* alert button */
+"Save (and notify members)" = "保存(并通知成员)";
+
+/* alert title */
+"Save admission settings?" = "保存入群设置?";
+
/* alert button */
"Save and notify contact" = "保存并通知联系人";
@@ -4225,6 +4595,9 @@ chat item action */
/* No comment provided by engineer. */
"Save group profile" = "保存群组资料";
+/* alert title */
+"Save group profile?" = "保存群资料?";
+
/* No comment provided by engineer. */
"Save list" = "保存列表";
@@ -4336,6 +4709,9 @@ chat item action */
/* chat item action */
"Select" = "选择";
+/* No comment provided by engineer. */
+"Select chat profile" = "选择聊天个人资料";
+
/* No comment provided by engineer. */
"Selected %lld" = "选定的 %lld";
@@ -4360,6 +4736,9 @@ chat item action */
/* No comment provided by engineer. */
"Send a live message - it will update for the recipient(s) as you type it" = "发送实时消息——它会在您键入时为收件人更新";
+/* No comment provided by engineer. */
+"Send contact request?" = "发送联络请求?";
+
/* No comment provided by engineer. */
"Send delivery receipts to" = "将送达回执发送给";
@@ -4390,18 +4769,30 @@ chat item action */
/* No comment provided by engineer. */
"Send notifications" = "发送通知";
+/* No comment provided by engineer. */
+"Send private reports" = "发送私下举报";
+
/* No comment provided by engineer. */
"Send questions and ideas" = "发送问题和想法";
/* No comment provided by engineer. */
"Send receipts" = "发送回执";
+/* No comment provided by engineer. */
+"Send request" = "发送请求";
+
+/* No comment provided by engineer. */
+"Send request without message" = "发送无消息请求";
+
/* No comment provided by engineer. */
"Send them from gallery or custom keyboards." = "发送它们来自图库或自定义键盘。";
/* No comment provided by engineer. */
"Send up to 100 last messages to new members." = "给新成员发送最多 100 条历史消息。";
+/* No comment provided by engineer. */
+"Send your private feedback to groups." = "向群发送私密反馈。";
+
/* alert message */
"Sender cancelled file transfer." = "发送人已取消文件传输。";
@@ -4459,6 +4850,12 @@ chat item action */
/* No comment provided by engineer. */
"Sent via proxy" = "通过代理发送";
+/* No comment provided by engineer. */
+"Server" = "服务器";
+
+/* alert message */
+"Server added to operator %@." = "服务器已添加到运营方 %@。";
+
/* No comment provided by engineer. */
"Server address" = "服务器地址";
@@ -4468,6 +4865,15 @@ chat item action */
/* srv error text. */
"Server address is incompatible with network settings." = "服务器地址与网络设置不兼容。";
+/* alert title */
+"Server operator changed." = "服务器运营方已更改。";
+
+/* No comment provided by engineer. */
+"Server operators" = "服务器运营方";
+
+/* alert title */
+"Server protocol changed." = "服务器协议已更改。";
+
/* queue info */
"server queue info: %@\n\nlast received msg: %@" = "服务器队列信息: %1$@\n\n上次收到的消息: %2$@";
@@ -4504,6 +4910,9 @@ chat item action */
/* No comment provided by engineer. */
"Set 1 day" = "设定1天";
+/* No comment provided by engineer. */
+"Set chat name…" = "设置聊天名称…";
+
/* No comment provided by engineer. */
"Set contact name…" = "设置联系人姓名……";
@@ -4516,6 +4925,12 @@ chat item action */
/* No comment provided by engineer. */
"Set it instead of system authentication." = "设置它以代替系统身份验证。";
+/* No comment provided by engineer. */
+"Set member admission" = "设置成员入群准许";
+
+/* No comment provided by engineer. */
+"Set message expiration in chats." = "在聊天中设置消息过期时间。";
+
/* profile update event chat item */
"set new contact address" = "设置新的联系地址";
@@ -4531,6 +4946,9 @@ chat item action */
/* No comment provided by engineer. */
"Set passphrase to export" = "设置密码来导出";
+/* No comment provided by engineer. */
+"Set profile bio and welcome message." = "设置自我介绍和欢迎消息。";
+
/* No comment provided by engineer. */
"Set the message shown to new members!" = "设置向新成员显示的消息!";
@@ -4540,6 +4958,9 @@ chat item action */
/* No comment provided by engineer. */
"Settings" = "设置";
+/* alert message */
+"Settings were changed." = "设置已修改。";
+
/* No comment provided by engineer. */
"Shape profile images" = "改变个人资料图形状";
@@ -4550,9 +4971,15 @@ chat item action */
/* No comment provided by engineer. */
"Share 1-time link" = "分享一次性链接";
+/* No comment provided by engineer. */
+"Share 1-time link with a friend" = "和一位好友分享一次性链接";
+
/* No comment provided by engineer. */
"Share address" = "分享地址";
+/* No comment provided by engineer. */
+"Share address publicly" = "公开分享地址";
+
/* alert title */
"Share address with contacts?" = "与联系人分享地址?";
@@ -4562,6 +4989,18 @@ chat item action */
/* No comment provided by engineer. */
"Share link" = "分享链接";
+/* alert button */
+"Share old address" = "分享旧地址";
+
+/* alert button */
+"Share old link" = "分享旧链接";
+
+/* No comment provided by engineer. */
+"Share profile" = "分享资料";
+
+/* No comment provided by engineer. */
+"Share SimpleX address on social media." = "在社媒上分享 SimpleX 地址。";
+
/* No comment provided by engineer. */
"Share this 1-time invite link" = "分享此一次性邀请链接";
@@ -4571,6 +5010,18 @@ chat item action */
/* No comment provided by engineer. */
"Share with contacts" = "与联系人分享";
+/* No comment provided by engineer. */
+"Share your address" = "分享地址";
+
+/* No comment provided by engineer. */
+"Short description" = "短描述";
+
+/* No comment provided by engineer. */
+"Short link" = "短链接";
+
+/* No comment provided by engineer. */
+"Short SimpleX address" = "SimpleX 短地址";
+
/* No comment provided by engineer. */
"Show → on messages sent via private routing." = "显示 → 通过专用路由发送的信息.";
@@ -4661,6 +5112,9 @@ chat item action */
/* No comment provided by engineer. */
"SimpleX protocols reviewed by Trail of Bits." = "SimpleX 协议由 Trail of Bits 审阅。";
+/* simplex link type */
+"SimpleX relay link" = "SimpleX 中继链接";
+
/* No comment provided by engineer. */
"Simplified incognito mode" = "简化的隐身模式";
@@ -4679,6 +5133,9 @@ chat item action */
/* No comment provided by engineer. */
"SMP server" = "SMP 服务器";
+/* No comment provided by engineer. */
+"SOCKS proxy" = "SOCKS代理";
+
/* blur media */
"Soft" = "软";
@@ -4694,9 +5151,16 @@ chat item action */
/* No comment provided by engineer. */
"Some non-fatal errors occurred during import:" = "导入过程中出现一些非致命错误:";
+/* alert message */
+"Some servers failed the test:\n%@" = "有服务器测试未通过:\n%@";
+
/* notification title */
"Somebody" = "某人";
+/* blocking reason
+report reason */
+"Spam" = "垃圾信息";
+
/* No comment provided by engineer. */
"Square, circle, or anything in between." = "方形、圆形、或两者之间的任意形状.";
@@ -4775,18 +5239,42 @@ chat item action */
/* No comment provided by engineer. */
"Support SimpleX Chat" = "支持 SimpleX Chat";
+/* No comment provided by engineer. */
+"Switch audio and video during the call." = "通话期间切换音频和视频。";
+
+/* No comment provided by engineer. */
+"Switch chat profile for 1-time invitations." = "对一次性邀请切换聊天个人资料。";
+
/* No comment provided by engineer. */
"System" = "系统";
/* No comment provided by engineer. */
"System authentication" = "系统验证";
+/* No comment provided by engineer. */
+"Tail" = "尾部";
+
/* No comment provided by engineer. */
"Take picture" = "拍照";
/* No comment provided by engineer. */
"Tap button " = "点击按钮 ";
+/* No comment provided by engineer. */
+"Tap Connect to chat" = "轻按连接进行聊天";
+
+/* No comment provided by engineer. */
+"Tap Connect to send request" = "轻按连接来发送请求";
+
+/* No comment provided by engineer. */
+"Tap Connect to use bot" = "轻按“连接”使用机器人";
+
+/* No comment provided by engineer. */
+"Tap Create SimpleX address in the menu to create it later." = "要稍后创建 SimpleX 地址,请在菜单中轻按“创建 SimpleX 地址”";
+
+/* No comment provided by engineer. */
+"Tap Join group" = "轻按加入群";
+
/* No comment provided by engineer. */
"Tap to activate profile." = "点击以激活个人资料。";
@@ -4808,9 +5296,15 @@ chat item action */
/* No comment provided by engineer. */
"TCP connection" = "TCP 连接";
+/* No comment provided by engineer. */
+"TCP connection bg timeout" = "TCP 连接后台超时";
+
/* No comment provided by engineer. */
"TCP connection timeout" = "TCP 连接超时";
+/* No comment provided by engineer. */
+"TCP port for messaging" = "用于消息收发的 TCP 端口";
+
/* No comment provided by engineer. */
"TCP_KEEPCNT" = "TCP_KEEPCNT";
@@ -4826,6 +5320,9 @@ chat item action */
/* server test failure */
"Test failed at step %@." = "在步骤 %@ 上测试失败。";
+/* No comment provided by engineer. */
+"Test notifications" = "测试通知";
+
/* No comment provided by engineer. */
"Test server" = "测试服务器";
@@ -4844,9 +5341,15 @@ chat item action */
/* No comment provided by engineer. */
"Thanks to the users – contribute via Weblate!" = "感谢用户——通过 Weblate 做出贡献!";
+/* alert message */
+"The address will be short, and your profile will be shared via the address." = "地址不会长,将通过该简短地址分享个人资料。";
+
/* No comment provided by engineer. */
"The app can notify you when you receive messages or contact requests - please open settings to enable." = "该应用可以在您收到消息或联系人请求时通知您——请打开设置以启用通知。";
+/* No comment provided by engineer. */
+"The app protects your privacy by using different operators in each conversation." = "应用通过在每个对话中使用不同运营方保护你的隐私。";
+
/* No comment provided by engineer. */
"The app will ask to confirm downloads from unknown file servers (except .onion)." = "该应用程序将要求确认从未知文件服务器(.onion 除外)下载。";
@@ -4856,6 +5359,9 @@ chat item action */
/* No comment provided by engineer. */
"The code you scanned is not a SimpleX link QR code." = "您扫描的码不是 SimpleX 链接的二维码。";
+/* No comment provided by engineer. */
+"The connection reached the limit of undelivered messages, your contact may be offline." = "连接达到了未送达消息上限,你的联系人可能处于离线状态。";
+
/* No comment provided by engineer. */
"The connection you accepted will be cancelled!" = "您接受的连接将被取消!";
@@ -4877,6 +5383,9 @@ chat item action */
/* No comment provided by engineer. */
"The ID of the next message is incorrect (less or equal to the previous).\nIt can happen because of some bug or when the connection is compromised." = "下一条消息的 ID 不正确(小于或等于上一条)。\n它可能是由于某些错误或连接被破坏才发生。";
+/* alert message */
+"The link will be short, and group profile will be shared via the link." = "链接不会长,群资料会通过短链接分享。";
+
/* No comment provided by engineer. */
"The message will be deleted for all members." = "将为所有成员删除该消息。";
@@ -4892,6 +5401,9 @@ chat item action */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "旧数据库在迁移过程中没有被移除,可以删除。";
+/* No comment provided by engineer. */
+"The second preset operator in the app!" = "应用中的第二个预设运营方!";
+
/* No comment provided by engineer. */
"The second tick we missed! ✅" = "我们错过的第二个\"√\"!✅";
@@ -4904,9 +5416,15 @@ chat item action */
/* No comment provided by engineer. */
"The text you pasted is not a SimpleX link." = "您粘贴的文本不是 SimpleX 链接。";
+/* No comment provided by engineer. */
+"The uploaded database archive will be permanently removed from the servers." = "已上传的数据库归档将会从服务器中永久移除。";
+
/* No comment provided by engineer. */
"Themes" = "主题";
+/* No comment provided by engineer. */
+"These conditions will also apply for: **%@**." = "这些条件将同样适用于: **%@**。";
+
/* No comment provided by engineer. */
"These settings are for your current profile **%@**." = "这些设置适用于您当前的配置文件 **%@**。";
@@ -4919,6 +5437,9 @@ chat item action */
/* No comment provided by engineer. */
"This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." = "此操作无法撤消——早于所选的发送和接收的消息将被删除。 这可能需要几分钟时间。";
+/* alert message */
+"This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted." = "此操作无法撤销 —— 比此聊天中所选消息更早发出并收到的消息将被删除。";
+
/* No comment provided by engineer. */
"This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "此操作无法撤消——您的个人资料、联系人、消息和文件将不可撤回地丢失。";
@@ -4943,12 +5464,24 @@ chat item action */
/* No comment provided by engineer. */
"This group no longer exists." = "该群组已不存在。";
+/* No comment provided by engineer. */
+"This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "此链接需要更新的应用版本。请升级应用或请求你的联系人发送相容的链接。";
+
/* No comment provided by engineer. */
"This link was used with another mobile device, please create a new link on the desktop." = "此链接已在其他移动设备上使用,请在桌面上创建新链接。";
+/* No comment provided by engineer. */
+"This message was deleted or not received yet." = "此消息被删除或尚未收到。";
+
/* No comment provided by engineer. */
"This setting applies to messages in your current chat profile **%@**." = "此设置适用于您当前聊天资料 **%@** 中的消息。";
+/* No comment provided by engineer. */
+"This setting is for your current profile **%@**." = "此设置用于当前个人资料 **%@**。";
+
+/* No comment provided by engineer. */
+"Time to disappear is set only for new contacts." = "只为新联系人设置了消失时间。";
+
/* No comment provided by engineer. */
"Title" = "标题";
@@ -4964,6 +5497,9 @@ chat item action */
/* No comment provided by engineer. */
"To make a new connection" = "建立新连接";
+/* No comment provided by engineer. */
+"To protect against your link being replaced, you can compare contact security codes." = "为了防止链接被替换,你可以比较联系人安全代码。";
+
/* No comment provided by engineer. */
"To protect timezone, image/voice files use UTC." = "为了保护时区,图像/语音文件使用 UTC。";
@@ -4976,15 +5512,36 @@ chat item action */
/* No comment provided by engineer. */
"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "为了保护隐私,SimpleX使用针对消息队列的标识符,而不是所有其他平台使用的用户ID,每个联系人都有独立的标识符。";
+/* No comment provided by engineer. */
+"To receive" = "消息接收";
+
+/* No comment provided by engineer. */
+"To record speech please grant permission to use Microphone." = "为了记录语音请授予使用麦克风权限。";
+
+/* No comment provided by engineer. */
+"To record video please grant permission to use Camera." = "为了录制视频请授予使用相机权限。";
+
/* No comment provided by engineer. */
"To record voice message please grant permission to use Microphone." = "请授权使用麦克风以录制语音消息。";
/* No comment provided by engineer. */
"To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page." = "要显示您的隐藏的个人资料,请在**您的聊天个人资料**页面的搜索字段中输入完整密码。";
+/* No comment provided by engineer. */
+"To send" = "发送";
+
+/* alert message */
+"To send commands you must be connected." = "你必须已连接才能发送命令。";
+
/* No comment provided by engineer. */
"To support instant push notifications the chat database has to be migrated." = "为了支持即时推送通知,聊天数据库必须被迁移。";
+/* alert message */
+"To use another profile after connection attempt, delete the chat and use the link again." = "要在连接尝试后使用不同的个人资料,请删除聊天并再次使用该链接。";
+
+/* No comment provided by engineer. */
+"To use the servers of **%@**, accept conditions of use." = "要使用**%@**的服务器,需接受条款。";
+
/* No comment provided by engineer. */
"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "要与您的联系人验证端到端加密,请比较(或扫描)您设备上的代码。";
@@ -5006,6 +5563,9 @@ chat item action */
/* No comment provided by engineer. */
"Transport sessions" = "传输会话";
+/* subscription status explanation */
+"Trying to connect to the server used to receive messages from this connection." = "尝试连接到用于从该连接接收消息的服务器。";
+
/* No comment provided by engineer. */
"Turkish interface" = "土耳其语界面";
@@ -5036,6 +5596,9 @@ chat item action */
/* rcv group event chat item */
"unblocked %@" = "未阻止 %@";
+/* No comment provided by engineer. */
+"Undelivered messages" = "未送达的消息";
+
/* No comment provided by engineer. */
"Unexpected migration state" = "未预料的迁移状态";
@@ -5102,6 +5665,9 @@ chat item action */
/* swipe action */
"Unread" = "未读";
+/* No comment provided by engineer. */
+"Unsupported connection link" = "不支持的连接链接";
+
/* No comment provided by engineer. */
"Up to 100 last messages are sent to new members." = "给新成员发送了最多 100 条历史消息。";
@@ -5117,6 +5683,9 @@ chat item action */
/* No comment provided by engineer. */
"Update settings?" = "更新设置?";
+/* No comment provided by engineer. */
+"Updated conditions" = "条款已更新";
+
/* rcv group event chat item */
"updated group profile" = "已更新的群组资料";
@@ -5126,9 +5695,27 @@ chat item action */
/* No comment provided by engineer. */
"Updating settings will re-connect the client to all servers." = "更新设置会将客户端重新连接到所有服务器。";
+/* alert button */
+"Upgrade" = "升级";
+
+/* No comment provided by engineer. */
+"Upgrade address" = "升级地址";
+
+/* alert message */
+"Upgrade address?" = "升级地址?";
+
/* No comment provided by engineer. */
"Upgrade and open chat" = "升级并打开聊天";
+/* alert message */
+"Upgrade group link?" = "升级群链接?";
+
+/* No comment provided by engineer. */
+"Upgrade link" = "升级链接";
+
+/* No comment provided by engineer. */
+"Upgrade your address" = "升级你的地址";
+
/* No comment provided by engineer. */
"Upload errors" = "上传错误";
@@ -5150,18 +5737,30 @@ chat item action */
/* No comment provided by engineer. */
"Use .onion hosts" = "使用 .onion 主机";
+/* No comment provided by engineer. */
+"Use %@" = "使用 %@";
+
/* No comment provided by engineer. */
"Use chat" = "使用聊天";
/* new chat action */
"Use current profile" = "使用当前配置文件";
+/* No comment provided by engineer. */
+"Use for files" = "用于文件";
+
+/* No comment provided by engineer. */
+"Use for messages" = "用于消息";
+
/* No comment provided by engineer. */
"Use for new connections" = "用于新连接";
/* No comment provided by engineer. */
"Use from desktop" = "从桌面端使用";
+/* No comment provided by engineer. */
+"Use incognito profile" = "使用隐身个人资料";
+
/* No comment provided by engineer. */
"Use iOS call interface" = "使用 iOS 通话界面";
@@ -5180,18 +5779,36 @@ chat item action */
/* No comment provided by engineer. */
"Use server" = "使用服务器";
+/* No comment provided by engineer. */
+"Use servers" = "使用服务器";
+
/* No comment provided by engineer. */
"Use SimpleX Chat servers?" = "使用 SimpleX Chat 服务器?";
+/* No comment provided by engineer. */
+"Use SOCKS proxy" = "使用 SOCKS 代理";
+
+/* No comment provided by engineer. */
+"Use TCP port %@ when no port is specified." = "当未指定端口时使用TCP端口%@。";
+
+/* No comment provided by engineer. */
+"Use TCP port 443 for preset servers only." = "仅预设服务器使用 TCP 协议 443 端口。";
+
/* No comment provided by engineer. */
"Use the app while in the call." = "通话时使用本应用.";
/* No comment provided by engineer. */
"Use the app with one hand." = "用一只手使用应用程序。";
+/* No comment provided by engineer. */
+"Use web port" = "使用 web 端口";
+
/* No comment provided by engineer. */
"User selection" = "用户选择";
+/* No comment provided by engineer. */
+"Username" = "用户名";
+
/* No comment provided by engineer. */
"Using SimpleX Chat servers." = "使用 SimpleX Chat 服务器。";
@@ -5258,9 +5875,15 @@ chat item action */
/* No comment provided by engineer. */
"Videos and files up to 1gb" = "最大 1gb 的视频和文件";
+/* No comment provided by engineer. */
+"View conditions" = "查看条款";
+
/* No comment provided by engineer. */
"View security code" = "查看安全码";
+/* No comment provided by engineer. */
+"View updated conditions" = "查看更新后的条款";
+
/* chat feature */
"Visible history" = "可见的历史";
@@ -5330,6 +5953,9 @@ chat item action */
/* No comment provided by engineer. */
"Welcome message is too long" = "欢迎消息太大了";
+/* No comment provided by engineer. */
+"Welcome your contacts 👋" = "欢迎联系人👋";
+
/* No comment provided by engineer. */
"What's new" = "更新内容";
@@ -5342,6 +5968,9 @@ chat item action */
/* No comment provided by engineer. */
"when IP hidden" = "当 IP 隐藏时";
+/* No comment provided by engineer. */
+"When more than one operator is enabled, none of them has metadata to learn who communicates with whom." = "当启用了超过一个运营方时,没有一个运营方拥有了解谁和谁联络的元数据。";
+
/* No comment provided by engineer. */
"When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "当您与某人共享隐身聊天资料时,该资料将用于他们邀请您加入的群组。";
@@ -5396,6 +6025,9 @@ chat item action */
/* No comment provided by engineer. */
"You accepted connection" = "您已接受连接";
+/* snd group event chat item */
+"you accepted this member" = "你接受了该成员";
+
/* No comment provided by engineer. */
"You allow" = "您允许";
@@ -5405,6 +6037,9 @@ chat item action */
/* No comment provided by engineer. */
"You are already connected to %@." = "您已经连接到 %@。";
+/* No comment provided by engineer. */
+"You are already connected with %@." = "你已经与%@保持连接。";
+
/* new chat sheet message */
"You are already connecting to %@." = "您已连接到 %@。";
@@ -5423,9 +6058,15 @@ chat item action */
/* new chat sheet title */
"You are already joining the group!\nRepeat join request?" = "您已经加入了这个群组!\n重复加入请求?";
+/* subscription status explanation */
+"You are connected to the server used to receive messages from this connection." = "你已连接到用于接收该连接消息的服务器。";
+
/* No comment provided by engineer. */
"You are invited to group" = "您被邀请加入群组";
+/* subscription status explanation */
+"You are not connected to the server used to receive messages from this connection (no subscription)." = "未连接到用于从该连接接收消息的服务器(无订阅)。";
+
/* No comment provided by engineer. */
"You are not connected to these servers. Private routing is used to deliver messages to them." = "您未连接到这些服务器。私有路由用于向他们发送消息。";
@@ -5441,6 +6082,9 @@ chat item action */
/* No comment provided by engineer. */
"You can change it in Appearance settings." = "您可以在外观设置中更改它。";
+/* No comment provided by engineer. */
+"You can configure servers via settings." = "你可以通过设置配置服务器。";
+
/* No comment provided by engineer. */
"You can create it later" = "您可以以后创建它";
@@ -5465,6 +6109,9 @@ chat item action */
/* No comment provided by engineer. */
"You can send messages to %@ from Archived contacts." = "您可以从存档的联系人向%@发送消息。";
+/* No comment provided by engineer. */
+"You can set connection name, to remember who the link was shared with." = "你可以设置连接名称,用来记住和谁分享了这个链接。";
+
/* No comment provided by engineer. */
"You can set lock screen notification preview via settings." = "您可以通过设置来设置锁屏通知预览。";
@@ -5489,6 +6136,9 @@ chat item action */
/* alert message */
"You can view invitation link again in connection details." = "您可以在连接详情中再次查看邀请链接。";
+/* alert message */
+"You can view your reports in Chat with admins." = "你可以在和管理员和聊天中查看你的举报。";
+
/* alert title */
"You can't send messages!" = "您无法发送消息!";
@@ -5561,6 +6211,9 @@ chat item action */
/* snd group event chat item */
"you unblocked %@" = "您解封了 %@";
+/* No comment provided by engineer. */
+"You will be able to send messages **only after your request is accepted**." = "**只有在你的请求被接受后**你才能发送消息。";
+
/* No comment provided by engineer. */
"You will be connected to group when the group host's device is online, please wait or check later!" = "您将在组主设备上线时连接到该群组,请稍等或稍后再检查!";
@@ -5579,6 +6232,9 @@ chat item action */
/* No comment provided by engineer. */
"You will still receive calls and notifications from muted profiles when they are active." = "当静音配置文件处于活动状态时,您仍会收到来自静音配置文件的电话和通知。";
+/* No comment provided by engineer. */
+"You will stop receiving messages from this chat. Chat history will be preserved." = "你将停止从这个聊天收到消息。聊天历史将被保留。";
+
/* No comment provided by engineer. */
"You will stop receiving messages from this group. Chat history will be preserved." = "您将停止接收来自该群组的消息。聊天记录将被保留。";
@@ -5594,6 +6250,9 @@ chat item action */
/* No comment provided by engineer. */
"You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "您正在为该群组使用隐身个人资料——为防止共享您的主要个人资料,不允许邀请联系人";
+/* No comment provided by engineer. */
+"Your business contact" = "你的企业联系人";
+
/* No comment provided by engineer. */
"Your calls" = "您的通话";
@@ -5603,9 +6262,15 @@ chat item action */
/* No comment provided by engineer. */
"Your chat database is not encrypted - set passphrase to encrypt it." = "您的聊天数据库未加密——设置密码来加密。";
+/* alert title */
+"Your chat preferences" = "你的聊天偏好设置";
+
/* No comment provided by engineer. */
"Your chat profiles" = "您的聊天资料";
+/* No comment provided by engineer. */
+"Your contact" = "你的联系人";
+
/* No comment provided by engineer. */
"Your contact sent a file that is larger than currently supported maximum size (%@)." = "您的联系人发送的文件大于当前支持的最大大小 (%@)。";
@@ -5615,12 +6280,18 @@ chat item action */
/* No comment provided by engineer. */
"Your contacts will remain connected." = "与您的联系人保持连接。";
+/* No comment provided by engineer. */
+"Your credentials may be sent unencrypted." = "你的凭据可能以未经加密的方式被发送。";
+
/* No comment provided by engineer. */
"Your current chat database will be DELETED and REPLACED with the imported one." = "您当前的聊天数据库将被删除并替换为导入的数据库。";
/* No comment provided by engineer. */
"Your current profile" = "您当前的资料";
+/* No comment provided by engineer. */
+"Your group" = "你的群";
+
/* No comment provided by engineer. */
"Your ICE servers" = "您的 ICE 服务器";
@@ -5642,12 +6313,18 @@ chat item action */
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "您的资料存储在您的设备上并仅与您的联系人共享。 SimpleX 服务器无法看到您的资料。";
+/* alert message */
+"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "您的个人资料已修改。如果进行保存,更新后的个人资料将发送到所有联系人。";
+
/* No comment provided by engineer. */
"Your random profile" = "您的随机资料";
/* No comment provided by engineer. */
"Your server address" = "您的服务器地址";
+/* No comment provided by engineer. */
+"Your servers" = "你的服务器";
+
/* No comment provided by engineer. */
"Your settings" = "您的设置";
diff --git a/apps/multiplatform/.gitignore b/apps/multiplatform/.gitignore
index f30061200c..5d39eb29f2 100644
--- a/apps/multiplatform/.gitignore
+++ b/apps/multiplatform/.gitignore
@@ -1,5 +1,6 @@
*.iml
.gradle
+.kotlin
/local.properties
/.idea
!/.idea/codeStyles/*
diff --git a/apps/multiplatform/CODE.md b/apps/multiplatform/CODE.md
new file mode 100644
index 0000000000..26a36e75bb
--- /dev/null
+++ b/apps/multiplatform/CODE.md
@@ -0,0 +1,309 @@
+# Coding and building
+
+You are an expert developer for SimpleX Chat, a privacy-first decentralized messaging platform. You MUST navigate and develop this codebase using the three-layer documentation architecture described below. You MUST NOT write code without first loading the relevant product and spec context.
+
+## Three-Layer Documentation Architecture
+
+### Why this structure exists
+
+LLMs start each session with no persistent understanding of the codebase. Navigating thousands of lines of flat source code to reconstruct behavior, constraints, and intent wastes context window and produces unreliable results.
+
+The `product/`, `spec/`, and source layers form a persistent, structured representation of the system that survives across sessions. Each layer is connected to the next by bidirectional cross-references. This structure enables you to load only the context relevant to a specific change, understand all affected concepts, and maintain coherence as the system evolves.
+
+### The layers
+
+| Layer | Contains | Question it answers |
+|-------|----------|-------------------|
+| `product/` | Capabilities, user flows, views, business rules, glossary | **What** does the system do and why? |
+| `spec/` | Technical design, API contracts, database schema, service internals | **How** is it organized technically? |
+| `common/src/commonMain/` | Shared Kotlin/Compose code (Android + Desktop) | What does it **execute** on both platforms? |
+| `common/src/androidMain/` | Android-specific Kotlin (platform implementations) | What does it execute on **Android**? |
+| `common/src/desktopMain/` | Desktop-specific Kotlin (platform implementations) | What does it execute on **Desktop**? |
+| `android/src/main/` | Android app module (Application, Activity, Services) | What is the **Android entry point**? |
+| `desktop/src/jvmMain/` | Desktop app module (main function) | What is the **Desktop entry point**? |
+| `../../src/Simplex/Chat/` | Haskell core (chat logic, protocol, database) | What does the **core** execute? |
+
+Each layer links to the next:
+- `product/concepts.md` links every concept to its spec docs, source files, and tests in a single table — this is the primary navigation entry point
+- `product/views/*.md` and `product/flows/*.md` each have a **Related spec:** line linking to their most relevant spec documents
+- `product/glossary.md` uses *See: [spec/...]* references and `product/rules.md` uses **Spec:** [spec/...] references to link individual terms and rules down to spec
+- `spec/` documents contain **Source:** headers and inline function links pointing down to source. Line references MUST be clickable by embedding the `#Lxx-Lyy` fragment in the link URL: [`functionName()`](common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#Lxx-Lyy). You MUST NOT duplicate line numbers in the display text — the URL fragment is sufficient. Why: redundant line numbers in display text create maintenance burden on every line shift.
+- Reverse direction: the Document Map (end of this file) maps source → spec → product
+
+### Navigation workflow
+
+When the user requests any change, you MUST follow these steps before writing any code:
+
+1. **Identify scope.** You MUST read `product/concepts.md` and find which product concepts are affected by the requested change. Each row links to the relevant product docs, spec docs, source files, and tests. Why: concepts.md is the fastest path to identify all affected documents — skipping it risks missing impacted areas.
+
+2. **Load product context.** You MUST read the relevant `product/views/*.md` or `product/flows/*.md` to understand current user-facing behavior. For business constraints, you MUST read `product/rules.md`. Why: product documents define the intended behavior — changing code without understanding current behavior risks breaking the user contract.
+
+3. **Load spec context.** You MUST follow the product → spec links to read the relevant `spec/*.md` or `spec/services/*.md`. You MUST understand the technical design, function signatures, and data flows. Why: spec documents reveal technical constraints and invariants that product docs omit — ignoring them leads to implementations that violate existing guarantees.
+
+4. **Load source context.** You MUST follow the spec → source links (with line numbers) to read the relevant source files. Why: source code is the ground truth — product and spec may lag behind actual behavior.
+
+5. **Identify full impact.** You MUST read `spec/impact.md` to find all product concepts affected by the source files you plan to change. This determines which documents you MUST update after the code change. Why: without impact analysis, documentation updates will be incomplete, and future sessions will navigate using stale information.
+
+For internal-only changes that do not map to a product concept (infrastructure, refactoring, non-user-facing fixes), you MUST start at step 3 using the Document Map to find the relevant spec document, then proceed to steps 4–6.
+
+6. **Implement.** Make the code change in source, then you MUST update all affected documentation as described in the Change Protocol below.
+
+### Key navigation documents
+
+| Document | Purpose | When to read |
+|----------|---------|-------------|
+| `product/concepts.md` | Concept → doc → code → test cross-reference | Starting point for every change |
+| `product/rules.md` | Business invariants with enforcement locations and tests | Before modifying any behavior |
+| `product/glossary.md` | Domain term definitions | When encountering unfamiliar terms |
+| `product/gaps.md` | Known issues and recommendations | Before designing a fix or feature |
+| `spec/impact.md` | Source file → affected product concepts | After identifying which files to change |
+| Document Map (below) | Source ↔ spec ↔ product mapping | When updating documentation |
+
+---
+
+## Code Security
+
+When designing code and planning implementations, you MUST:
+- Apply adversarial thinking, and consider what may happen if one of the communicating parties is malicious. Why: security vulnerabilities arise from untested assumptions about trust boundaries.
+- Formulate an explicit threat model for each change — who can do which undesirable things and under which circumstances. Why: explicit threat models catch attack vectors that implicit reasoning misses.
+
+---
+
+## Code Style
+
+**Follow existing code patterns — you MUST:**
+- Match the style of surrounding code. Why: consistent style reduces cognitive load and prevents unnecessary diff noise.
+- Use Kotlin data classes for value types, regular classes for reference types, and sealed classes/interfaces for variants. Why: correct type choices leverage the type system for compile-time correctness.
+- Prefer exhaustive `when` expressions over `else` branches. Why: `else` branches bypass compiler checks for new sealed subclasses and hide bugs.
+
+**Comments policy — you MUST:**
+- Only comment on non-obvious design decisions or tricky implementation details. Why: redundant comments create maintenance burden and drift from code.
+- Keep function names and type signatures self-documenting. Why: good names eliminate the need for most comments.
+- Assume a competent Kotlin reader. Why: over-explaining trivial Kotlin adds noise without value.
+
+**Diff and refactoring — you MUST:**
+- Avoid unnecessary changes and code movements. Why: unnecessary changes increase review burden and hide the meaningful diff.
+- Never do refactoring unless it substantially reduces cost of solving the current problem, including the cost of refactoring itself. Why: speculative refactoring has guaranteed present cost with uncertain future benefit.
+- Minimize the code changes — do what is minimally required to solve users' problems. Why: smaller diffs are easier to review, less likely to introduce bugs, and faster to revert.
+
+**Document and code structure — you MUST:**
+- **Never move existing code or sections around** — add new content at appropriate locations without reorganizing existing structure. Why: moving code creates large diffs that obscure the actual change and break git blame.
+- When adding new sections to documents, continue the existing numbering scheme. Why: consistent numbering preserves document navigability.
+- Minimize diff size — prefer small, targeted changes over reorganization. Why: large diffs compound review errors and make rollback difficult.
+
+**Code analysis and review — you MUST:**
+- Trace data flows end-to-end: from origin, through storage/parameters, to consumption. Flag values that are discarded and reconstructed from partial data (e.g. extracted from a URI missing original fields) — this is usually a bug. Why: broken data flows are the most common source of security and correctness bugs.
+- Read implementations of called functions, not just signatures — if duplication involves a called function, check whether decomposing it resolves the duplication. Why: function signatures can be misleading about actual behavior.
+- Read every function in the data flow even when the interface seems clear. Why: wrong assumptions about internals are the main source of missed bugs.
+
+---
+
+## Plans
+
+When developing via plans (non-trivial features, multi-step changes, architectural decisions), you MUST store the plan in the `plans/` folder before implementing. Why: plans are the persistent record of design decisions and rationale — without them, future sessions cannot understand why the system was built the way it was.
+
+### Plan requirements
+
+1. **File naming.** You MUST use the format `YYYYMMDD_NN.md` (e.g., `20260211_01.md`). Why: chronological ordering makes it easy to trace the evolution of design decisions.
+
+2. **Plan structure.** Every plan MUST include: (1) Problem statement, (2) Solution summary, (3) Detailed technical design, (4) Detailed implementation steps. Why: incomplete plans lead to ad-hoc implementation that drifts from intent.
+
+3. **Consistency with product/ and spec/.** The plan MUST be consistent with the current state of `product/` and `spec/`. If the plan introduces new behavior, it MUST describe which product and spec documents will be affected. Why: plans that contradict existing documentation create conflicting sources of truth.
+
+4. **Adversarial self-review.** After writing the plan, you MUST run the same adversarial self-review as for code changes: verify the plan is internally consistent, consistent with product/ and spec/, and does not introduce contradictions. You MUST repeat until two consecutive passes find zero issues. Why: an incoherent plan produces incoherent implementation.
+
+---
+
+## Change Protocol
+
+### The rule
+
+Every code change MUST include corresponding updates to `spec/` and `product/`. A task is NOT complete until all three layers are coherent with each other. Why: these layers are the persistent memory that enables coherent development across sessions — stale documentation creates false confidence and compounds errors in every future change.
+
+### What to update
+
+1. **spec/ — on every code change.** You MUST update the corresponding spec document to reflect the change. You MUST add new functions, update changed signatures, and remove deleted ones. Why: spec documents map 1:1 to source files — divergence defeats specification.
+
+2. **product/ — when user-visible behavior changes.** You MUST update the relevant `product/views/*.md` and any affected `product/flows/*.md`. You MUST update `product/rules.md` when business invariants change. Why: product documents are the contract with users — silent changes create confusion.
+
+3. **Line number references — on every code change.** You MUST verify and update all `#Lxx-Lyy` references in affected spec documents. Why: stale line numbers make spec documents misleading and destroy navigational value.
+
+4. **Cross-references — when adding or removing files.** You MUST add corresponding spec documents and update `spec/README.md` document index and reverse index. When adding pages, you MUST add `product/views/` and `spec/client/` documents. You MUST update the Document Map at the end of this file. Why: every source file must be covered for the navigation system to work.
+
+5. **Impact graph — when adding files or changing what a file affects.** You MUST update `spec/impact.md` to reflect the source file → product concept mapping. Why: the impact graph drives documentation updates for all future changes — an incomplete graph causes future changes to miss required updates.
+
+6. **Concept index — when adding or changing product concepts.** You MUST add or update the relevant row in `product/concepts.md` with links to product docs, spec docs, source files, and tests. Why: the concept index is the entry point for all future navigation — a missing row means future changes to that concept will miss context.
+
+7. **[GAP] annotations — when discovering issues.** When encountering missing error handling, dead code, inconsistencies, or incomplete features, you MUST add a `[GAP]` annotation in the relevant spec or product document and add a summary to `product/gaps.md`. Why: this builds institutional knowledge about technical debt.
+
+8. **[REC] annotations — when identifying improvements.** You MUST add a `[REC]` annotation in the relevant document. Why: capturing improvement ideas at discovery time preserves context that is lost later.
+
+9. **Preserve document structure.** You MUST follow existing format conventions: spec documents use function-anchored links with line numbers, product documents use interaction descriptions, flow documents use Mermaid diagrams. Why: consistent structure makes documents predictable and navigable.
+
+### Adversarial self-review
+
+After completing all changes (code + documentation), you MUST run an adversarial self-review. You MUST check coherence both within each layer and across layers.
+
+**Within-layer coherence — you MUST verify:**
+- spec/ is internally consistent — no contradictory descriptions, state machines have no unreachable states, data model is referentially intact
+- product/ is internally consistent — flows match views, rules match behavior descriptions
+
+**Across-layer coherence — you MUST verify:**
+- Every new or changed function in source appears in the corresponding spec/ document
+- Every user-visible behavior change in source appears in the relevant product/ document
+- All `#Lxx-Lyy` line references in affected spec documents point to the correct lines
+- All cross-references resolve — product → spec links, spec → source links
+- `spec/impact.md` covers all affected product concepts for the changed source files
+- `product/concepts.md` rows are current for any affected concepts
+
+**Convergence:** You MUST repeat the review-and-fix cycle until two consecutive passes find zero issues. You MUST fix all issues discovered between passes. Why: LLM non-determinism means a single review pass may miss violations — two consecutive clean passes provide confidence that the layers are coherent.
+
+---
+
+## Multiplatform Architecture Notes
+
+### Kotlin Multiplatform (KMP) + Compose Multiplatform
+
+The app uses Kotlin Multiplatform with Compose Multiplatform for shared UI. The project has three Gradle modules:
+
+- **common/** — Shared library containing all models, views, platform abstractions, and theme system
+- **android/** — Android app module (Application, Activity, Services)
+- **desktop/** — Desktop JVM app module (main entry point)
+
+### expect/actual Pattern
+
+Platform-specific code uses Kotlin's `expect`/`actual` mechanism. The `commonMain` source set declares `expect` functions/classes, and `androidMain`/`desktopMain` provide `actual` implementations. Files follow the naming convention:
+- `commonMain`: `FileName.kt` (contains `expect` declarations)
+- `androidMain`: `FileName.android.kt` (contains `actual` implementations)
+- `desktopMain`: `FileName.desktop.kt` (contains `actual` implementations)
+
+When modifying platform abstractions, you MUST update both `actual` implementations.
+
+### Source Set Structure
+
+```
+common/src/
+├── commonMain/kotlin/chat/simplex/common/ -- Shared code (195 files)
+│ ├── model/ -- ChatModel, SimpleXAPI, CryptoFile
+│ ├── platform/ -- expect/actual platform abstractions
+│ ├── ui/theme/ -- Theme system (ThemeManager, colors, types)
+│ └── views/ -- Compose UI (chat, chatlist, call, settings, etc.)
+├── androidMain/kotlin/chat/simplex/common/ -- Android actuals (55 files)
+│ ├── platform/ -- actual implementations
+│ └── views/ -- Android-specific view variants
+├── desktopMain/kotlin/chat/simplex/common/ -- Desktop actuals (56 files)
+│ ├── platform/ -- actual implementations
+│ └── views/ -- Desktop-specific view variants
+android/src/main/java/chat/simplex/app/ -- Android app (8 files)
+desktop/src/jvmMain/kotlin/chat/simplex/desktop/ -- Desktop app (1 file)
+```
+
+### Platform Differences
+
+| Aspect | Android | Desktop |
+|--------|---------|---------|
+| Layout | 2-column (chat list → chat) | 3-column (sidebar → chat list → details) |
+| Background messaging | SimplexService (foreground service) + MessagesFetcherWorker (WorkManager) | Continuous (always-on process) |
+| Notifications | Android NotificationManager with channels | Desktop system notifications |
+| Calls | CallActivity (separate Activity) + CallService | In-window call view |
+| Video playback | ExoPlayer | VLC (VLCJ) |
+| Authentication | Android BiometricPrompt | Passcode only |
+| Auto-update | Play Store / manual APK | Built-in AppUpdater |
+| Window management | Standard Activity lifecycle | StoreWindowState persistence |
+| Entry point | SimplexApp (Application) + MainActivity | Main.kt → initHaskell() → showApp() |
+
+---
+
+## Document Map
+
+### Shared Sources (commonMain)
+
+| Source Location | Spec Document | Product Document |
+|----------------|---------------|-----------------|
+| common/.../common/App.kt | spec/architecture.md | product/views/chat-list.md |
+| common/.../common/AppLock.kt | spec/architecture.md | product/views/settings.md |
+| common/.../common/model/ChatModel.kt | spec/state.md | product/concepts.md |
+| common/.../common/model/SimpleXAPI.kt | spec/api.md, spec/architecture.md | product/concepts.md |
+| common/.../common/model/CryptoFile.kt | spec/services/files.md | product/flows/file-transfer.md |
+| common/.../common/platform/Core.kt | spec/architecture.md | product/concepts.md |
+| common/.../common/platform/AppCommon.kt | spec/architecture.md | product/flows/onboarding.md |
+| common/.../common/platform/Notifications.kt | spec/services/notifications.md | product/flows/messaging.md |
+| common/.../common/platform/NtfManager.kt | spec/services/notifications.md | product/flows/messaging.md |
+| common/.../common/platform/Files.kt | spec/services/files.md | product/flows/file-transfer.md |
+| common/.../common/platform/SimplexService.kt | spec/services/notifications.md | product/flows/messaging.md |
+| common/.../common/platform/Share.kt | spec/architecture.md | product/concepts.md |
+| common/.../common/platform/VideoPlayer.kt | spec/services/files.md | product/views/chat.md |
+| common/.../common/platform/RecAndPlay.kt | spec/services/files.md | product/views/chat.md |
+| common/.../common/platform/UI.kt | spec/architecture.md | product/views/chat.md |
+| common/.../common/platform/Platform.kt | spec/architecture.md | product/concepts.md |
+| common/.../common/ui/theme/ThemeManager.kt | spec/services/theme.md | product/views/settings.md |
+| common/.../common/ui/theme/Theme.kt | spec/services/theme.md | product/views/settings.md |
+| common/.../common/ui/theme/Color.kt | spec/services/theme.md | product/views/settings.md |
+| common/.../common/views/chatlist/ChatListView.kt | spec/client/chat-list.md | product/views/chat-list.md |
+| common/.../common/views/chatlist/ChatListNavLinkView.kt | spec/client/chat-list.md | product/views/chat-list.md |
+| common/.../common/views/chatlist/ChatPreviewView.kt | spec/client/chat-list.md | product/views/chat-list.md |
+| common/.../common/views/chatlist/UserPicker.kt | spec/client/chat-list.md | product/views/chat-list.md |
+| common/.../common/views/chatlist/TagListView.kt | spec/client/chat-list.md | product/views/chat-list.md |
+| common/.../common/views/chat/ChatView.kt | spec/client/chat-view.md | product/views/chat.md |
+| common/.../common/views/chat/ComposeView.kt | spec/client/compose.md | product/views/chat.md |
+| common/.../common/views/chat/SendMsgView.kt | spec/client/compose.md | product/views/chat.md |
+| common/.../common/views/chat/ChatInfoView.kt | spec/client/chat-view.md | product/views/contact-info.md |
+| common/.../common/views/chat/group/ | spec/client/chat-view.md | product/views/group-info.md |
+| common/.../common/views/chat/item/ | spec/client/chat-view.md | product/views/chat.md |
+| common/.../common/views/call/CallView.kt | spec/services/calls.md | product/views/call.md |
+| common/.../common/views/call/IncomingCallAlertView.kt | spec/services/calls.md | product/views/call.md |
+| common/.../common/views/call/WebRTC.kt | spec/services/calls.md | product/flows/calling.md |
+| common/.../common/views/newchat/NewChatView.kt | spec/client/navigation.md | product/views/new-chat.md |
+| common/.../common/views/newchat/AddGroupView.kt | spec/client/navigation.md | product/views/new-chat.md |
+| common/.../common/views/usersettings/SettingsView.kt | spec/client/navigation.md | product/views/settings.md |
+| common/.../common/views/usersettings/Appearance.kt | spec/services/theme.md | product/views/settings.md |
+| common/.../common/views/usersettings/PrivacySettings.kt | spec/client/navigation.md | product/views/settings.md |
+| common/.../common/views/usersettings/networkAndServers/ | spec/architecture.md | product/views/settings.md |
+| common/.../common/views/usersettings/UserProfilesView.kt | spec/client/navigation.md | product/views/user-profiles.md |
+| common/.../common/views/onboarding/ | spec/client/navigation.md | product/views/onboarding.md |
+| common/.../common/views/localauth/ | spec/architecture.md | product/views/settings.md |
+| common/.../common/views/database/ | spec/database.md | product/views/settings.md |
+| common/.../common/views/migration/ | spec/database.md | product/flows/onboarding.md |
+| common/.../common/views/remote/ | spec/architecture.md | product/views/settings.md |
+| common/.../common/views/contacts/ | spec/client/chat-view.md | product/views/contact-info.md |
+| common/.../common/views/helpers/ | spec/architecture.md | product/concepts.md |
+
+### Android-Specific Sources
+
+| Source Location | Spec Document | Product Document |
+|----------------|---------------|-----------------|
+| android/.../app/SimplexApp.kt | spec/architecture.md | product/flows/onboarding.md |
+| android/.../app/MainActivity.kt | spec/architecture.md | product/views/chat-list.md |
+| android/.../app/SimplexService.kt | spec/services/notifications.md | product/flows/messaging.md |
+| android/.../app/CallService.kt | spec/services/calls.md | product/flows/calling.md |
+| android/.../app/MessagesFetcherWorker.kt | spec/services/notifications.md | product/flows/messaging.md |
+| android/.../app/model/NtfManager.android.kt | spec/services/notifications.md | product/flows/messaging.md |
+| android/.../app/views/call/CallActivity.kt | spec/services/calls.md | product/views/call.md |
+
+### Desktop-Specific Sources
+
+| Source Location | Spec Document | Product Document |
+|----------------|---------------|-----------------|
+| desktop/.../desktop/Main.kt | spec/architecture.md | product/flows/onboarding.md |
+| common/.../common/DesktopApp.kt (desktopMain) | spec/architecture.md | product/views/chat-list.md |
+| common/.../common/StoreWindowState.kt (desktopMain) | spec/architecture.md | product/views/settings.md |
+| common/.../common/model/NtfManager.desktop.kt (desktopMain) | spec/services/notifications.md | product/flows/messaging.md |
+| common/.../common/views/helpers/AppUpdater.kt (desktopMain) | spec/architecture.md | product/views/settings.md |
+
+### Haskell Core Sources (at `../../src/Simplex/Chat/` relative to `apps/multiplatform/`)
+
+| Source Location | Spec Document | Product Document |
+|----------------|---------------|-----------------|
+| ../../src/Simplex/Chat/Controller.hs | spec/api.md | product/concepts.md |
+| ../../src/Simplex/Chat/Types.hs | spec/api.md | product/glossary.md |
+| ../../src/Simplex/Chat/Core.hs | spec/architecture.md | product/concepts.md |
+| ../../src/Simplex/Chat/Protocol.hs | spec/architecture.md | product/concepts.md |
+| ../../src/Simplex/Chat/Messages.hs | spec/api.md | product/flows/messaging.md |
+| ../../src/Simplex/Chat/Messages/CIContent.hs | spec/api.md | product/flows/messaging.md |
+| ../../src/Simplex/Chat/Call.hs | spec/services/calls.md | product/flows/calling.md |
+| ../../src/Simplex/Chat/Files.hs | spec/services/files.md | product/flows/file-transfer.md |
+| ../../src/Simplex/Chat/Store/Messages.hs | spec/database.md | product/flows/messaging.md |
+| ../../src/Simplex/Chat/Store/Groups.hs | spec/database.md | product/flows/group-lifecycle.md |
+| ../../src/Simplex/Chat/Store/Direct.hs | spec/database.md | product/flows/connection.md |
+| ../../src/Simplex/Chat/Store/Files.hs | spec/database.md | product/flows/file-transfer.md |
+| ../../src/Simplex/Chat/Store/Profiles.hs | spec/database.md | product/views/user-profiles.md |
diff --git a/apps/multiplatform/README.md b/apps/multiplatform/README.md
index e8b0e086c9..eef1048ada 100644
--- a/apps/multiplatform/README.md
+++ b/apps/multiplatform/README.md
@@ -1,8 +1,105 @@
# Android App Development
-This readme is currently a stub and as such is in development.
+This is a guide to contributing to the develop of the SimpleX android and desktop apps.
-Ultimately, this readme will act as a guide to contributing to the develop of the SimpleX android app.
+## Project Overview
+
+This is the **Kotlin Multiplatform (KMP)** mobile and desktop client for SimpleX Chat, sharing code between Android and Desktop (JVM) platforms using Compose Multiplatform for UI.
+
+## Build Commands
+
+```bash
+# Android debug APK
+./gradlew assembleDebug
+
+# Android release APK
+./gradlew assembleRelease
+
+# Desktop distribution (current OS)
+./gradlew :desktop:packageDistributionForCurrentOS
+
+# Run desktop/JVM tests
+./gradlew desktopTest
+
+# Run Android instrumented tests (requires connected device/emulator)
+./gradlew connectedAndroidTest
+
+# Build native libraries for all platforms
+./gradlew common:cmakeBuild -PcrossCompile
+
+# Clean build
+./gradlew clean
+```
+
+## Architecture
+
+### Module Structure
+
+- **`common/`** - Shared code (Compose UI, models, business logic)
+ - `src/commonMain/` - Cross-platform code
+ - `src/androidMain/` - Android-specific implementations
+ - `src/desktopMain/` - Desktop-specific implementations
+- **`android/`** - Android app container
+- **`desktop/`** - Desktop JVM app container
+
+### Key Components (`common/src/commonMain/kotlin/chat/simplex/common/`)
+
+- **`model/ChatModel.kt`** - Main state container with reactive properties (MutableState, MutableStateFlow)
+- **`model/SimpleXAPI.kt`** - API bindings to Haskell core library via FFI
+- **`platform/Core.kt`** - FFI interface to native `libapp` library
+- **`platform/`** - Platform abstraction layer (expect/actual pattern for Android/Desktop specifics)
+- **`views/`** - Compose UI screens organized by feature (chat, chatlist, call, usersettings, etc.)
+- **`ui/theme/`** - Design system (colors, typography, shapes)
+
+### Native Integration
+
+The app calls into a Haskell core library via JNI/FFI:
+- CMake builds in `common/src/commonMain/cpp/android/` and `cpp/desktop/`
+- Cross-compilation toolchains in `cpp/toolchains/`
+- Built libraries go to `cpp/desktop/libs/` (organized by platform)
+
+## Configuration
+
+### `local.properties` (create from `local.properties.example`)
+
+```properties
+compression.level=0 # APK compression (0-9)
+enable_debuggable=true # Debug mode
+application_id.suffix=.debug # Multiple app instances on same device
+app.name=SimpleX Debug # App name for debug builds
+```
+
+### `gradle.properties`
+
+Contains versions (Kotlin, Compose, AGP) and app version info. Key settings:
+- `kotlin.jvm.target=11`
+- `database.backend=sqlite` (or `postgres`)
+
+## Testing
+
+Tests are in:
+- `common/src/commonTest/kotlin/` - Cross-platform tests
+- `common/src/desktopTest/kotlin/` - Desktop-specific tests (run with `./gradlew desktopTest`)
+- `android/src/androidTest/` - Android instrumented tests
+
+## Resources & Localization
+
+- String resources: `common/src/commonMain/resources/MR/base/strings.xml` + 21 language variants
+- Uses Moko Resources (`dev.icerock.moko:resources`) for cross-platform resource management
+- The `adjustFormatting` gradle task validates string resources during build
+
+## Platform-Specific Notes
+
+### Android
+- Min SDK 26, Target SDK 35
+- NDK 23.1.7779620
+- Supports ABI splits: `arm64-v8a`, `armeabi-v7a`
+- Deep linking requires SHA certificate fingerprint in `assetlinks.json` (see README.md)
+
+### Desktop
+- Distributions: DMG (macOS), MSI/EXE (Windows), DEB (Linux)
+- Mac signing/notarization configured via `local.properties`
+- Video playback uses VLCJ
## Gotchas
diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt
index 22e53af849..56279a5143 100644
--- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt
+++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt
@@ -108,6 +108,7 @@ class ActiveCallState: Closeable {
}
+// Spec: spec/services/calls.md#ActiveCallView
@SuppressLint("SourceLockedOrientationActivity")
@Composable
actual fun ActiveCallView() {
diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/Utils.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/Utils.android.kt
index 9d1e0c4e97..a5021ae54c 100644
--- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/Utils.android.kt
+++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/Utils.android.kt
@@ -182,6 +182,8 @@ private fun spannableStringToAnnotatedString(
actual fun getAppFileUri(fileName: String): URI =
FileProvider.getUriForFile(androidAppContext, "$APPLICATION_ID.provider", if (File(fileName).isAbsolute) File(fileName) else File(getAppFilePath(fileName))).toURI()
+actual fun clearImageCaches() {}
+
// https://developer.android.com/training/data-storage/shared/documents-files#bitmap
actual suspend fun getLoadedImage(file: CIFile?): Pair? {
val filePath = getLoadedFilePath(file)
diff --git a/apps/multiplatform/common/src/commonMain/cpp/desktop/CMakeLists.txt b/apps/multiplatform/common/src/commonMain/cpp/desktop/CMakeLists.txt
index 059e5af426..1ebfdce6b1 100644
--- a/apps/multiplatform/common/src/commonMain/cpp/desktop/CMakeLists.txt
+++ b/apps/multiplatform/common/src/commonMain/cpp/desktop/CMakeLists.txt
@@ -54,11 +54,10 @@ add_library( # Sets the name of the library.
simplex-api.c)
add_library( simplex SHARED IMPORTED )
+FILE(GLOB SIMPLEXLIB ${CMAKE_SOURCE_DIR}/libs/${OS_LIB_PATH}-${OS_LIB_ARCH}/libsimplex.${OS_LIB_EXT})
if(WIN32)
- FILE(GLOB SIMPLEXLIB ${CMAKE_SOURCE_DIR}/libs/${OS_LIB_PATH}-${OS_LIB_ARCH}/lib*simplex*.${OS_LIB_EXT})
set_target_properties( simplex PROPERTIES IMPORTED_IMPLIB ${SIMPLEXLIB})
else()
- FILE(GLOB SIMPLEXLIB ${CMAKE_SOURCE_DIR}/libs/${OS_LIB_PATH}-${OS_LIB_ARCH}/lib*simplex-chat*.${OS_LIB_EXT})
set_target_properties( simplex PROPERTIES IMPORTED_LOCATION ${SIMPLEXLIB})
endif()
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt
index 70e0067260..d9439a5474 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt
@@ -42,6 +42,7 @@ import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
+// Spec: spec/client/navigation.md#AppScreen
@Composable
fun AppScreen() {
AppBarHandler.appBarMaxHeightPx = with(LocalDensity.current) { AppBarHeight.roundToPx() }
@@ -78,6 +79,7 @@ fun AppScreen() {
}
}
+// Spec: spec/client/navigation.md#MainScreen
@Composable
fun MainScreen() {
val chatModel = ChatModel
@@ -289,6 +291,7 @@ fun AndroidWrapInCallLayout(content: @Composable () -> Unit) {
}
}
+// Spec: spec/client/navigation.md#AndroidScreen
@Composable
fun AndroidScreen(userPickerState: MutableStateFlow) {
BoxWithConstraints {
@@ -402,6 +405,7 @@ fun EndPartOfScreen() {
ModalManager.end.showInView()
}
+// Spec: spec/client/navigation.md#DesktopScreen
@Composable
fun DesktopScreen(userPickerState: MutableStateFlow) {
Box(Modifier.width(DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier)) {
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt
index d6f9640cb9..32a5ce1ef1 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt
@@ -13,6 +13,7 @@ import chat.simplex.common.views.usersettings.*
import chat.simplex.res.MR
import kotlinx.coroutines.*
+// Spec: spec/client/navigation.md#AppLock
object AppLock {
/**
* We don't want these values to be bound to Activity lifecycle since activities are changed often, for example, when a user
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 8db2cc1a76..3d6b227df7 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
@@ -81,6 +81,7 @@ val connectProgressManager = ConnectProgressManager
/*
* Without this annotation an animation from ChatList to ChatView has 1 frame per the whole animation. Don't delete it
* */
+// Spec: spec/state.md#ChatModel
@Stable
object ChatModel {
val controller: ChatController = ChatController
@@ -334,6 +335,7 @@ object ChatModel {
}
}
+ // Spec: spec/state.md#ChatsContext
class ChatsContext(val secondaryContextFilter: SecondaryContextFilter?) {
val chats = mutableStateOf(SnapshotStateList())
/** if you modify the items by adding/removing them, use helpers methods like [addToChatItems], [removeLastChatItems], [removeAllAndNotify], [clearAndNotify] and so on.
@@ -1321,6 +1323,7 @@ interface SomeChat {
val updatedAt: Instant
}
+// Spec: spec/state.md#Chat
@Serializable @Stable
data class Chat(
val remoteHostId: Long?,
@@ -1362,6 +1365,7 @@ data class Chat(
true
}
+ // Spec: spec/state.md#ChatStats
@Serializable
data class ChatStats(
val unreadCount: Int = 0,
@@ -1382,6 +1386,7 @@ data class Chat(
}
}
+// Spec: spec/state.md#ChatInfo
@Serializable
sealed class ChatInfo: SomeChat, NamedChat {
@@ -1899,6 +1904,12 @@ data class Connection(
val connInactive: Boolean
get() = quotaErrCounter >= 5 // quotaErrInactiveCount in core
+ val connFailedErr: String?
+ get() = when (connStatus) {
+ is ConnStatus.Failed -> connStatus.connError
+ else -> null
+ }
+
val connPQEnabled: Boolean
get() = pqSndEnabled == true && pqRcvEnabled == true
@@ -2633,25 +2644,27 @@ class PendingContactConnection(
}
@Serializable
-enum class ConnStatus {
- @SerialName("new") New,
- @SerialName("prepared") Prepared,
- @SerialName("joined") Joined,
- @SerialName("requested") Requested,
- @SerialName("accepted") Accepted,
- @SerialName("snd-ready") SndReady,
- @SerialName("ready") Ready,
- @SerialName("deleted") Deleted;
+sealed class ConnStatus {
+ @Serializable @SerialName("new") object New: ConnStatus()
+ @Serializable @SerialName("prepared") object Prepared: ConnStatus()
+ @Serializable @SerialName("joined") object Joined: ConnStatus()
+ @Serializable @SerialName("requested") object Requested: ConnStatus()
+ @Serializable @SerialName("accepted") object Accepted: ConnStatus()
+ @Serializable @SerialName("sndReady") object SndReady: ConnStatus()
+ @Serializable @SerialName("ready") object Ready: ConnStatus()
+ @Serializable @SerialName("deleted") object Deleted: ConnStatus()
+ @Serializable @SerialName("failed") class Failed(val connError: String): ConnStatus()
val initiated: Boolean? get() = when (this) {
- New -> true
- Prepared -> false
- Joined -> false
- Requested -> true
- Accepted -> true
- SndReady -> null
- Ready -> null
- Deleted -> null
+ is New -> true
+ is Prepared -> false
+ is Joined -> false
+ is Requested -> true
+ is Accepted -> true
+ is SndReady -> null
+ is Ready -> null
+ is Deleted -> null
+ is Failed -> null
}
}
@@ -4350,6 +4363,7 @@ sealed class Format {
@Serializable @SerialName("strikeThrough") class StrikeThrough: Format()
@Serializable @SerialName("snippet") class Snippet: Format()
@Serializable @SerialName("secret") class Secret: Format()
+ @Serializable @SerialName("small") class Small: Format()
@Serializable @SerialName("colored") class Colored(val color: FormatColor): Format()
@Serializable @SerialName("uri") class Uri: Format()
@Serializable @SerialName("hyperLink") class HyperLink(val showText: String?, val linkUri: String): Format()
@@ -4371,6 +4385,7 @@ sealed class Format {
is StrikeThrough -> SpanStyle(textDecoration = TextDecoration.LineThrough)
is Snippet -> SpanStyle(fontFamily = FontFamily.Monospace)
is Secret -> SpanStyle(color = Color.Transparent, background = SecretColor)
+ is Small -> SpanStyle(fontSize = MaterialTheme.typography.body2.fontSize, color = MaterialTheme.colors.secondary)
is Colored -> SpanStyle(color = this.color.uiColor)
is Uri -> linkStyle
is HyperLink -> linkStyle
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/CryptoFile.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/CryptoFile.kt
index 6ef56a9124..60f5c9e2ca 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/CryptoFile.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/CryptoFile.kt
@@ -20,6 +20,7 @@ sealed class WriteFileResult {
}
* */
+// Spec: spec/services/files.md#writeCryptoFile
fun writeCryptoFile(path: String, data: ByteArray): CryptoFileArgs {
val ctrl = ChatController.getChatCtrl() ?: throw Exception("Controller is not initialized")
val buffer = ByteBuffer.allocateDirect(data.size)
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt
index a244293edb..388a8064c4 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt
@@ -90,6 +90,7 @@ enum class SimplexLinkMode {
}
}
+// Spec: spec/state.md#AppPreferences
class AppPreferences {
// deprecated, remove in 2024
private val runServiceInBackground = mkBoolPreference(SHARED_PREFS_RUN_SERVICE_IN_BACKGROUND, true)
@@ -491,6 +492,7 @@ private const val MESSAGE_TIMEOUT: Int = 300_000_000
object ChatController {
private var chatCtrl: ChatCtrl? = -1
+ // Spec: spec/state.md#appPrefs
val appPrefs: AppPreferences by lazy { AppPreferences() }
val messagesChannel: Channel = Channel()
@@ -654,6 +656,7 @@ object ChatController {
chatModel.updateChatTags(rhId)
}
+ // Spec: spec/api.md#startReceiver
private fun startReceiver() {
Log.d(TAG, "ChatController startReceiver")
if (receiverJob != null || chatCtrl == null) return
@@ -797,6 +800,7 @@ object ChatController {
return null
}
+ // Spec: spec/api.md#sendCmd
suspend fun sendCmd(rhId: Long?, cmd: CC, otherCtrl: ChatCtrl? = null, retryNum: Int = 0, log: Boolean = true): API {
val ctrl = otherCtrl ?: chatCtrl ?: throw Exception("Controller is not initialized")
@@ -821,6 +825,7 @@ object ChatController {
}
}
+ // Spec: spec/api.md#recvMsg
fun recvMsg(ctrl: ChatCtrl): API? {
val rStr = chatRecvMsgWait(ctrl, MESSAGE_TIMEOUT)
return if (rStr == "") {
@@ -1036,6 +1041,14 @@ object ChatController {
return null
}
+ suspend fun apiGetChatContentTypes(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?): List? {
+ val r = sendCmd(rh, CC.ApiGetChatContentTypes(type, id, scope))
+ if (r is API.Result && r.res is CR.ChatContentTypes) return r.res.contentTypes
+ Log.e(TAG, "apiGetChatContentTypes bad response: ${r.responseType} ${r.details}")
+ AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_loading_details), "${r.responseType}: ${r.details}")
+ return null
+ }
+
suspend fun apiCreateChatTag(rh: Long?, tag: ChatTagData): List? {
val r = sendCmd(rh, CC.ApiCreateChatTag(tag))
if (r is API.Result && r.res is CR.ChatTags) return r.res.userTags
@@ -2551,6 +2564,7 @@ object ChatController {
AlertManager.shared.showAlertMsg(title, errMsg)
}
+ // Spec: spec/api.md#processReceivedMsg
private suspend fun processReceivedMsg(msg: API) {
lastMsgReceivedTimestamp = System.currentTimeMillis()
val rhId = msg.rhId
@@ -3511,6 +3525,7 @@ class SharedPreference(val get: () -> T, set: (T) -> Unit) {
}
// ChatCommand
+// Spec: spec/api.md#CC
sealed class CC {
class Console(val cmd: String): CC()
class ShowActiveUser: CC()
@@ -3542,6 +3557,7 @@ sealed class CC {
class ApiGetChatTags(val userId: Long): CC()
class ApiGetChats(val userId: Long): CC()
class ApiGetChat(val type: ChatType, val id: Long, val scope: GroupChatScope?, val contentTag: MsgContentTag?, val pagination: ChatPagination, val search: String = ""): CC()
+ class ApiGetChatContentTypes(val type: ChatType, val id: Long, val scope: GroupChatScope?): CC()
class ApiGetChatItemInfo(val type: ChatType, val id: Long, val scope: GroupChatScope?, val itemId: Long): CC()
class ApiSendMessages(val type: ChatType, val id: Long, val scope: GroupChatScope?, val live: Boolean, val ttl: Int?, val composedMessages: List): CC()
class ApiCreateChatTag(val tag: ChatTagData): CC()
@@ -3726,6 +3742,7 @@ sealed class CC {
}
"/_get chat ${chatRef(type, id, scope)}$tag ${pagination.cmdString}" + (if (search == "") "" else " search=$search")
}
+ is ApiGetChatContentTypes -> "/_get content types ${chatRef(type, id, scope)}"
is ApiGetChatItemInfo -> "/_get item info ${chatRef(type, id, scope)} $itemId"
is ApiSendMessages -> {
val msgs = json.encodeToString(composedMessages)
@@ -3912,6 +3929,7 @@ sealed class CC {
is ApiGetChatTags -> "apiGetChatTags"
is ApiGetChats -> "apiGetChats"
is ApiGetChat -> "apiGetChat"
+ is ApiGetChatContentTypes -> "apiGetChatContentTypes"
is ApiGetChatItemInfo -> "apiGetChatItemInfo"
is ApiSendMessages -> "apiSendMessages"
is ApiCreateChatTag -> "apiCreateChatTag"
@@ -4139,9 +4157,11 @@ class UpdatedMessage(val msgContent: MsgContent, val mentions: Map
@Serializable
class ChatTagData(val emoji: String?, val text: String)
+// Spec: spec/api.md#ArchiveConfig
@Serializable
class ArchiveConfig(val archivePath: String, val disableCompression: Boolean? = null, val parentTempDirectory: String? = null)
+// Spec: spec/database.md#DBEncryptionConfig
@Serializable
class DBEncryptionConfig(val currentKey: String, val newKey: String)
@@ -5949,6 +5969,7 @@ val yaml = Yaml(configuration = YamlConfiguration(
codePointLimit = 5500000,
))
+// Spec: spec/api.md#API
@Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
@Serializable(with = APISerializer::class)
sealed class API {
@@ -6088,6 +6109,7 @@ private fun decodeObject(deserializer: DeserializationStrategy, obj: Json
runCatching { json.decodeFromJsonElement(deserializer, obj!!) }.getOrNull()
// ChatResponse
+// Spec: spec/api.md#CR
@Serializable
sealed class CR {
@Serializable @SerialName("activeUser") class ActiveUser(val user: User): CR()
@@ -6097,6 +6119,7 @@ sealed class CR {
@Serializable @SerialName("chatStopped") class ChatStopped: CR()
@Serializable @SerialName("apiChats") class ApiChats(val user: UserRef, val chats: List): CR()
@Serializable @SerialName("apiChat") class ApiChat(val user: UserRef, val chat: Chat, val navInfo: NavigationInfo = NavigationInfo()): CR()
+ @Serializable @SerialName("chatContentTypes") class ChatContentTypes(val contentTypes: List): CR()
@Serializable @SerialName("chatTags") class ChatTags(val user: UserRef, val userTags: List): CR()
@Serializable @SerialName("chatItemInfo") class ApiChatItemInfo(val user: UserRef, val chatItem: AChatItem, val chatItemInfo: ChatItemInfo): CR()
@Serializable @SerialName("serverTestResult") class ServerTestResult(val user: UserRef, val testServer: String, val testFailure: ProtocolTestFailure? = null): CR()
@@ -6278,6 +6301,7 @@ sealed class CR {
is ChatStopped -> "chatStopped"
is ApiChats -> "apiChats"
is ApiChat -> "apiChat"
+ is ChatContentTypes -> "chatContentTypes"
is ChatTags -> "chatTags"
is ApiChatItemInfo -> "chatItemInfo"
is ServerTestResult -> "serverTestResult"
@@ -6451,6 +6475,7 @@ sealed class CR {
is ChatStopped -> noDetails()
is ApiChats -> withUser(user, json.encodeToString(chats))
is ApiChat -> withUser(user, "remoteHostId: ${chat.remoteHostId}\nchatInfo: ${chat.chatInfo}\nchatStats: ${chat.chatStats}\nnavInfo: ${navInfo}\nchatItems: ${chat.chatItems}")
+ is ChatContentTypes -> "content types: ${json.encodeToString(contentTypes)}"
is ChatTags -> withUser(user, "userTags: ${json.encodeToString(userTags)}")
is ApiChatItemInfo -> withUser(user, "chatItem: ${json.encodeToString(chatItem)}\n${json.encodeToString(chatItemInfo)}")
is ServerTestResult -> withUser(user, "server: $testServer\nresult: ${json.encodeToString(testFailure)}")
@@ -6944,6 +6969,7 @@ data class RemoteFile(
val fileSource: CryptoFile
)
+// Spec: spec/api.md#ChatError
@Serializable
sealed class ChatError {
val string: String get() = when (this) {
@@ -7627,6 +7653,7 @@ sealed class RCErrorType {
@Serializable @SerialName("syntax") data class SYNTAX(val syntaxErr: String): RCErrorType()
}
+// Spec: spec/database.md#ArchiveError
@Serializable
sealed class ArchiveError {
val string: String get() = when (this) {
@@ -7708,6 +7735,7 @@ sealed class RemoteCtrlError {
@Serializable @SerialName("protocolError") object ProtocolError: RemoteCtrlError()
}
+// Spec: spec/services/notifications.md#NotificationsMode
enum class NotificationsMode() {
OFF, PERIODIC, SERVICE, /*INSTANT - for Firebase notifications */;
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt
index d0ce703033..36a7ae1a80 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt
@@ -14,12 +14,14 @@ import java.io.File
import java.nio.ByteBuffer
// ghc's rts
+// Spec: spec/architecture.md#initHS
external fun initHS()
// android-support
external fun pipeStdOutToSocket(socketName: String) : Int
// SimpleX API
typealias ChatCtrl = Long
+// Spec: spec/architecture.md#chatMigrateInit
external fun chatMigrateInit(dbPath: String, dbKey: String, confirm: String): Array
external fun chatCloseStore(ctrl: ChatCtrl): String
external fun chatSendCmdRetry(ctrl: ChatCtrl, msg: String, retryNum: Int): String
@@ -45,6 +47,7 @@ val appPreferences: AppPreferences
val chatController: ChatController = ChatController
+// Spec: spec/architecture.md#initChatControllerOnStart
fun initChatControllerOnStart() {
withLongRunningApi {
if (appPreferences.chatStopped.get() && appPreferences.storeDBPassphrase.get() && ksDatabasePassword.get() != null) {
@@ -55,6 +58,7 @@ fun initChatControllerOnStart() {
}
}
+// Spec: spec/architecture.md#initChatController
suspend fun initChatController(useKey: String? = null, confirmMigrations: MigrationConfirmation? = null, startChat: () -> CompletableDeferred = { CompletableDeferred(true) }) {
Log.d(TAG, "initChatController")
try {
@@ -182,6 +186,7 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat
}
}
+// Spec: spec/architecture.md#chatInitTemporaryDatabase
fun chatInitTemporaryDatabase(dbPath: String, key: String? = null, confirmation: MigrationConfirmation = MigrationConfirmation.Error): Pair {
val dbKey = key ?: randomDatabasePassword()
Log.d(TAG, "chatInitTemporaryDatabase path: $dbPath")
@@ -193,6 +198,7 @@ fun chatInitTemporaryDatabase(dbPath: String, key: String? = null, confirmation:
return res to migrated[1] as ChatCtrl
}
+// Spec: spec/architecture.md#chatInitControllerRemovingDatabases
fun chatInitControllerRemovingDatabases() {
val dbPath = dbAbsolutePrefixPath
// Remove previous databases, otherwise, can be .errorNotADatabase with null controller
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt
index 0a4f670fe0..88d9fbb705 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt
@@ -14,6 +14,7 @@ import java.net.URLEncoder
import java.nio.file.Files
import java.nio.file.StandardCopyOption
+// Spec: spec/services/files.md#dataDir
expect val dataDir: File
expect val tmpDir: File
expect val filesDir: File
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt
index d906ef7baf..39fcea3981 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt
@@ -13,6 +13,7 @@ enum class NotificationAction {
ACCEPT_CONTACT_REQUEST
}
+// Spec: spec/services/notifications.md#ntfManager
lateinit var ntfManager: NtfManager
abstract class NtfManager {
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt
index df9af7fbf6..1b5a81a819 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt
@@ -22,6 +22,7 @@ import chat.simplex.res.MR
import kotlinx.serialization.Transient
import java.util.UUID
+// Spec: spec/services/theme.md#DefaultTheme
enum class DefaultTheme {
LIGHT, DARK, SIMPLEX, BLACK;
@@ -47,6 +48,7 @@ enum class DefaultThemeMode {
@SerialName("dark") DARK
}
+// Spec: spec/services/theme.md#AppColors
@Stable
class AppColors(
title: Color,
@@ -99,6 +101,7 @@ class AppColors(
}
}
+// Spec: spec/services/theme.md#AppWallpaper
@Stable
class AppWallpaper(
background: Color? = null,
@@ -133,6 +136,7 @@ class AppWallpaper(
}
}
+// Spec: spec/services/theme.md#ThemeColor
enum class ThemeColor {
PRIMARY, PRIMARY_VARIANT, SECONDARY, SECONDARY_VARIANT, BACKGROUND, SURFACE, TITLE, SENT_MESSAGE, SENT_QUOTE, RECEIVED_MESSAGE, RECEIVED_QUOTE, PRIMARY_VARIANT2, WALLPAPER_BACKGROUND, WALLPAPER_TINT;
@@ -174,6 +178,7 @@ enum class ThemeColor {
}
}
+// Spec: spec/services/theme.md#ThemeColors
@Serializable
data class ThemeColors(
@SerialName("accent")
@@ -214,6 +219,7 @@ data class ThemeColors(
}
}
+// Spec: spec/services/theme.md#ThemeWallpaper
@Serializable
data class ThemeWallpaper (
val preset: String? = null,
@@ -293,6 +299,7 @@ data class ThemesFile(
val themes: List = emptyList()
)
+// Spec: spec/services/theme.md#ThemeOverrides
@Serializable
data class ThemeOverrides (
val themeId: String = UUID.randomUUID().toString(),
@@ -463,6 +470,7 @@ fun List.skipDuplicates(): List {
return res
}
+// Spec: spec/services/theme.md#ThemeModeOverrides
@Serializable
data class ThemeModeOverrides (
val light: ThemeModeOverride? = null,
@@ -474,6 +482,7 @@ data class ThemeModeOverrides (
}
}
+// Spec: spec/services/theme.md#ThemeModeOverride
@Serializable
data class ThemeModeOverride (
val mode: DefaultThemeMode = CurrentColors.value.base.mode,
@@ -714,6 +723,7 @@ val BlackColorPaletteApp = AppColors(
var systemInDarkThemeCurrently: Boolean = isInNightMode()
+// Spec: spec/services/theme.md#CurrentColors
val CurrentColors: MutableStateFlow = MutableStateFlow(ThemeManager.currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPreferences.themeOverrides.get()))
@Composable
@@ -758,6 +768,7 @@ fun reactOnDarkThemeChanges(isDark: Boolean) {
}
}
+// Spec: spec/services/theme.md#SimpleXTheme
@Composable
fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) {
// TODO: Fix preview working with dark/light theme
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/ThemeManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/ThemeManager.kt
index 07f2b678cf..7d8c79b4a8 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/ThemeManager.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/ThemeManager.kt
@@ -53,6 +53,7 @@ object ThemeManager {
?: ThemeWallpaper.from(PresetWallpaper.SCHOOL.toType(CurrentColors.value.base), null, null))
}
+ // Spec: spec/services/theme.md#currentColors
fun currentColors(themeOverridesForType: WallpaperType?, perChatTheme: ThemeModeOverride?, perUserTheme: ThemeModeOverrides?, appSettingsTheme: List): ActiveTheme {
val themeName = appPrefs.currentTheme.get()!!
val nonSystemThemeName = nonSystemThemeName()
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallView.kt
index 8f5aba138d..7a92bc8c39 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallView.kt
@@ -6,6 +6,7 @@ import chat.simplex.common.model.ChatModel.controller
import chat.simplex.common.platform.*
import kotlinx.coroutines.*
+// Spec: spec/services/calls.md#ActiveCallView
@Composable
expect fun ActiveCallView()
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/IncomingCallAlertView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/IncomingCallAlertView.kt
index 4d8c1fae46..563f4c3b83 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/IncomingCallAlertView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/IncomingCallAlertView.kt
@@ -22,6 +22,7 @@ import chat.simplex.common.views.usersettings.ProfilePreview
import chat.simplex.res.MR
import kotlinx.datetime.Clock
+// Spec: spec/services/calls.md#IncomingCallAlertView
@Composable
fun IncomingCallAlertView(invitation: RcvCallInvitation, chatModel: ChatModel) {
val cm = chatModel.callManager
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt
index 705fc6a28f..6fa99283d8 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt
@@ -46,6 +46,7 @@ data class Call(
get() = localMediaSources.hasVideo || peerMediaSources.hasVideo
}
+// Spec: spec/services/calls.md#CallState
enum class CallState {
WaitCapabilities,
InvitationSent,
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt
index ed40150cb1..107d427556 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt
@@ -27,11 +27,12 @@ suspend fun apiLoadMessages(
chatType: ChatType,
apiId: Long,
pagination: ChatPagination,
+ contentTag: MsgContentTag? = null,
search: String = "",
openAroundItemId: Long? = null,
visibleItemIndexesNonReversed: () -> IntRange = { 0 .. 0 }
) = coroutineScope {
- val (chat, navInfo) = chatModel.controller.apiGetChat(rhId, chatType, apiId, chatsCtx.groupScopeInfo?.toChatScope(), chatsCtx.contentTag, pagination, search) ?: return@coroutineScope
+ val (chat, navInfo) = chatModel.controller.apiGetChat(rhId, chatType, apiId, chatsCtx.groupScopeInfo?.toChatScope(), contentTag ?: chatsCtx.contentTag, pagination, search) ?: return@coroutineScope
// For .initial allow the chatItems to be empty as well as chatModel.chatId to not match this chat because these values become set after .initial finishes
/** When [openAroundItemId] is provided, chatId can be different too */
if (((chatModel.chatId.value != chat.id || chat.chatItems.isEmpty()) && pagination !is ChatPagination.Initial && pagination !is ChatPagination.Last && openAroundItemId == null)
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt
index 26daee363f..c518b156d9 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt
@@ -45,6 +45,7 @@ import chat.simplex.common.views.newchat.ContactConnectionInfoView
import chat.simplex.common.views.newchat.alertProfileImageSize
import chat.simplex.res.MR
import dev.icerock.moko.resources.ImageResource
+import dev.icerock.moko.resources.StringResource
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.datetime.*
@@ -92,6 +93,7 @@ fun ConnectInProgressView(s: String) {
@Composable
// staleChatId means the id that was before chatModel.chatId becomes null. It's needed for Android only to make transition from chat
// to chat list smooth. Otherwise, chat view will become blank right before the transition starts
+// Spec: spec/client/chat-view.md#ChatView
fun ChatView(
chatsCtx: ChatModel.ChatsContext,
staleChatId: State,
@@ -144,6 +146,9 @@ fun ChatView(
val scope = rememberCoroutineScope()
val selectedChatItems = rememberSaveable { mutableStateOf(null as Set?) }
val showCommandsMenu = rememberSaveable { mutableStateOf(false) }
+ val contentFilter = rememberSaveable { mutableStateOf(null) }
+ val availableContent = remember { mutableStateOf>(ContentFilter.initialList) }
+
if (appPlatform.isAndroid) {
DisposableEffect(Unit) {
onDispose {
@@ -170,7 +175,13 @@ fun ChatView(
}
showSearch.value = false
searchText.value = ""
+ contentFilter.value = null
+ availableContent.value = ContentFilter.initialList
selectedChatItems.value = null
+ val cInfo = activeChat.value?.chatInfo
+ if (chatsCtx.secondaryContextFilter == null && (cInfo is ChatInfo.Direct || cInfo is ChatInfo.Group || cInfo is ChatInfo.Local)) {
+ updateAvailableContent(chatRh, activeChat, availableContent)
+ }
if (chat.chatInfo is ChatInfo.Direct && chat.chatInfo.contact.activeConn != null) {
withBGApi {
val r = chatModel.controller.apiContactInfo(chatRh, chatInfo.apiId)
@@ -229,11 +240,11 @@ fun ChatView(
val sameText = searchText.value == value
// showSearch can be false with empty text when it was closed manually after clicking on message from search to load .around it
// (required on Android to have this check to prevent call to search with old text)
- val emptyAndClosedSearch = searchText.value.isEmpty() && !showSearch.value && chatsCtx.secondaryContextFilter == null
+ val emptyAndClosedSearch = searchText.value.isEmpty() && !showSearch.value && chatsCtx.secondaryContextFilter == null && contentFilter.value == null
val c = chatModel.getChat(chatInfo.id)
- if (sameText || emptyAndClosedSearch || c == null || chatModel.chatId.value != chatInfo.id) return@onSearchValueChanged
+ if ((sameText && contentFilter.value == null) || emptyAndClosedSearch || c == null || chatModel.chatId.value != chatInfo.id) return@onSearchValueChanged
withBGApi {
- apiFindMessages(chatsCtx, c, value)
+ apiFindMessages(chatsCtx, c, contentFilter.value?.contentTag, value)
searchText.value = value
}
}
@@ -486,7 +497,7 @@ fun ChatView(
val c = chatModel.getChat(chatId)
if (chatModel.chatId.value != chatId) return@ChatLayout
if (c != null) {
- apiLoadMessages(chatsCtx, c.remoteHostId, c.chatInfo.chatType, c.chatInfo.apiId, pagination, searchText.value, null, visibleItemIndexes)
+ apiLoadMessages(chatsCtx, c.remoteHostId, c.chatInfo.chatType, c.chatInfo.apiId, pagination, contentFilter.value?.contentTag, searchText.value, null, visibleItemIndexes)
}
},
deleteMessage = { itemId, mode ->
@@ -742,14 +753,24 @@ fun ChatView(
changeNtfsState = { enabled, currentValue -> toggleNotifications(chatRh, chatInfo, enabled, chatModel, currentValue) },
onSearchValueChanged = onSearchValueChanged,
closeSearch = {
+ onSearchValueChanged("")
showSearch.value = false
searchText.value = ""
+ contentFilter.value = null
+ // Update available content types when search closes
+ val cInfo = activeChat.value?.chatInfo
+ if (chatsCtx.secondaryContextFilter == null && (cInfo is ChatInfo.Direct || cInfo is ChatInfo.Group || cInfo is ChatInfo.Local)) {
+ updateAvailableContent(chatRh, activeChat, availableContent)
+ }
},
onComposed,
developerTools = chatModel.controller.appPrefs.developerTools.get(),
showViaProxy = chatModel.controller.appPrefs.showSentViaProxy.get(),
showSearch = showSearch,
- showCommandsMenu = showCommandsMenu
+ showCommandsMenu = showCommandsMenu,
+ contentFilter = contentFilter,
+ availableContent = availableContent,
+ searchPlaceholder = contentFilter.value?.searchPlaceholder?.let { generalGetString(it) }
)
}
}
@@ -785,6 +806,23 @@ fun ChatView(
}
}
+fun updateAvailableContent(chatRh: Long?, activeChat: State, availableContent: MutableState>) {
+ withBGApi {
+ Log.e(TAG, "updateAvailableContent")
+ val chatInfo = activeChat.value?.chatInfo
+ if (chatInfo == null) return@withBGApi
+ val types = chatModel.controller.apiGetChatContentTypes(chatRh, chatInfo.chatType, chatInfo.apiId, null)
+ if (activeChat.value?.chatInfo?.id != chatInfo.id) return@withBGApi
+ if (types == null) {
+ availableContent.value = ContentFilter.entries
+ } else {
+ val typeSet: Set = types.union(ContentFilter.alwaysShow)
+ Log.e(TAG, "updateAvailableContent $typeSet")
+ availableContent.value = ContentFilter.entries.filter { it -> typeSet.contains(it.contentTag) }
+ }
+ }
+}
+
private fun connectingText(chatInfo: ChatInfo): String? {
return when (chatInfo) {
is ChatInfo.Direct ->
@@ -879,7 +917,10 @@ fun ChatLayout(
developerTools: Boolean,
showViaProxy: Boolean,
showSearch: MutableState,
- showCommandsMenu: MutableState
+ showCommandsMenu: MutableState,
+ contentFilter: MutableState,
+ availableContent: State>,
+ searchPlaceholder: String?
) {
val chatInfo = remember { derivedStateOf { chat.value?.chatInfo } }
val scope = rememberCoroutineScope()
@@ -1063,7 +1104,7 @@ fun ChatLayout(
Box {
if (selectedChatItems.value == null) {
if (chatInfo != null) {
- ChatInfoToolbar(chatsCtx, chatInfo, back, info, startCall, endCall, addMembers, openGroupLink, changeNtfsState, onSearchValueChanged, showSearch)
+ ChatInfoToolbar(chatsCtx, chatInfo, back, info, startCall, endCall, addMembers, openGroupLink, changeNtfsState, onSearchValueChanged, showSearch, contentFilter, availableContent, searchPlaceholder)
}
} else {
SelectedItemsCounterToolbar(selectedChatItems, !oneHandUI.value || !chatBottomBar.value)
@@ -1096,10 +1137,14 @@ fun BoxScope.ChatInfoToolbar(
openGroupLink: (GroupInfo) -> Unit,
changeNtfsState: (MsgFilter, currentValue: MutableState) -> Unit,
onSearchValueChanged: (String) -> Unit,
- showSearch: MutableState
+ showSearch: MutableState,
+ contentFilter: MutableState,
+ availableContent: State>,
+ searchPlaceholder: String?
) {
val scope = rememberCoroutineScope()
val showMenu = rememberSaveable { mutableStateOf(false) }
+ val showContentFilterMenu = rememberSaveable { mutableStateOf(false) }
val onBackClicked = {
if (!showSearch.value) {
@@ -1107,6 +1152,7 @@ fun BoxScope.ChatInfoToolbar(
} else {
onSearchValueChanged("")
showSearch.value = false
+ contentFilter.value = null
}
}
if (appPlatform.isAndroid && chatsCtx.secondaryContextFilter == null) {
@@ -1115,104 +1161,141 @@ fun BoxScope.ChatInfoToolbar(
val barButtons = arrayListOf<@Composable RowScope.() -> Unit>()
val menuItems = arrayListOf<@Composable () -> Unit>()
val activeCall by remember { chatModel.activeCall }
- if (chatInfo is ChatInfo.Local) {
- barButtons.add {
- IconButton(
- {
- showMenu.value = false
- showSearch.value = true
- }, enabled = chatInfo.noteFolder.ready
- ) {
- Icon(
- painterResource(MR.images.ic_search),
- stringResource(MR.strings.search_verb).capitalize(Locale.current),
- tint = if (chatInfo.noteFolder.ready) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
- )
- }
- }
- } else {
- menuItems.add {
- ItemAction(stringResource(MR.strings.search_verb), painterResource(MR.images.ic_search), onClick = {
- showMenu.value = false
- showSearch.value = true
- })
- }
- }
- if (chatInfo is ChatInfo.Direct && chatInfo.contact.mergedPreferences.calls.enabled.forUser) {
- if (activeCall == null) {
- barButtons.add {
- IconButton({
- showMenu.value = false
- startCall(CallMediaType.Audio)
- }, enabled = chatInfo.contact.ready && chatInfo.contact.active
- ) {
- Icon(
- painterResource(MR.images.ic_call_500),
- stringResource(MR.strings.icon_descr_audio_call).capitalize(Locale.current),
- tint = if (chatInfo.contact.ready && chatInfo.contact.active) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
- )
- }
- }
- } else if (activeCall?.contact?.id == chatInfo.id && appPlatform.isDesktop) {
- barButtons.add {
- val call = remember { chatModel.activeCall }.value
- val connectedAt = call?.connectedAt
- if (connectedAt != null) {
- val time = remember { mutableStateOf(durationText(0)) }
- LaunchedEffect(Unit) {
- while (true) {
- time.value = durationText((Clock.System.now() - connectedAt).inWholeSeconds.toInt())
- delay(250)
- }
- }
- val sp50 = with(LocalDensity.current) { 50.sp.toDp() }
- Text(time.value, Modifier.widthIn(min = sp50))
- }
- }
- barButtons.add {
- IconButton({
- showMenu.value = false
- endCall()
- }) {
- Icon(
- painterResource(MR.images.ic_call_end_filled),
- null,
- tint = MaterialTheme.colors.error
- )
- }
- }
- }
- if (chatInfo.contact.ready && chatInfo.contact.active && activeCall == null) {
+ val showContentFilterButton = availableContent.value.isNotEmpty()
+ val activeCallInChat = chatInfo is ChatInfo.Direct && activeCall?.contact?.id == chatInfo.id
+
+ // Content filter button - shown in bar, or moved to menu during active call
+ if (showContentFilterButton) {
+ val enabled = chatInfo !is ChatInfo.Local || chatInfo.noteFolder.ready
+ if (activeCallInChat) {
menuItems.add {
- ItemAction(stringResource(MR.strings.icon_descr_video_call).capitalize(Locale.current), painterResource(MR.images.ic_videocam), onClick = {
- showMenu.value = false
- startCall(CallMediaType.Video)
- })
- }
- }
- } else if (chatInfo is ChatInfo.Group && chatInfo.groupInfo.canAddMembers) {
- if (!chatInfo.incognito) {
- barButtons.add {
- IconButton({
- showMenu.value = false
- addMembers(chatInfo.groupInfo)
- }) {
- Icon(painterResource(MR.images.ic_person_add_500), stringResource(MR.strings.icon_descr_add_members), tint = MaterialTheme.colors.primary)
- }
+ ItemAction(
+ stringResource(MR.strings.content_filter_menu_item),
+ painterResource(MR.images.ic_photo_library),
+ onClick = {
+ showMenu.value = false
+ showContentFilterMenu.value = true
+ }
+ )
}
} else {
barButtons.add {
- IconButton({
- showMenu.value = false
- openGroupLink(chatInfo.groupInfo)
- }) {
- Icon(painterResource(MR.images.ic_add_link), stringResource(MR.strings.group_link), tint = MaterialTheme.colors.primary)
+ IconButton(
+ { showContentFilterMenu.value = true },
+ enabled = enabled
+ ) {
+ Icon(
+ painterResource(MR.images.ic_photo_library),
+ null,
+ tint = MaterialTheme.colors.primary
+ )
}
}
}
}
+ // Chat-type specific buttons
+ when (chatInfo) {
+ is ChatInfo.Local -> {
+ barButtons.add {
+ IconButton(
+ {
+ showMenu.value = false
+ showSearch.value = true
+ }, enabled = chatInfo.noteFolder.ready
+ ) {
+ Icon(
+ painterResource(MR.images.ic_search),
+ stringResource(MR.strings.search_verb).capitalize(Locale.current),
+ tint = if (chatInfo.noteFolder.ready) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
+ )
+ }
+ }
+ }
+ is ChatInfo.Direct -> {
+ if (activeCall?.contact?.id == chatInfo.id) {
+ if (appPlatform.isDesktop) {
+ barButtons.add {
+ val call = remember { chatModel.activeCall }.value
+ val connectedAt = call?.connectedAt
+ if (connectedAt != null) {
+ val time = remember { mutableStateOf(durationText(0)) }
+ LaunchedEffect(Unit) {
+ while (true) {
+ time.value = durationText((Clock.System.now() - connectedAt).inWholeSeconds.toInt())
+ delay(250)
+ }
+ }
+ val sp50 = with(LocalDensity.current) { 50.sp.toDp() }
+ Text(time.value, Modifier.widthIn(min = sp50))
+ }
+ }
+ }
+ barButtons.add {
+ IconButton({
+ showMenu.value = false
+ endCall()
+ }) {
+ Icon(
+ painterResource(MR.images.ic_call_end_filled),
+ null,
+ tint = MaterialTheme.colors.error
+ )
+ }
+ }
+ }
+ // Call buttons moved to menu
+ if (chatInfo.contact.mergedPreferences.calls.enabled.forUser && chatInfo.contact.ready && chatInfo.contact.active && activeCall == null) {
+ menuItems.add {
+ ItemAction(stringResource(MR.strings.icon_descr_audio_call).capitalize(Locale.current), painterResource(MR.images.ic_call_500), onClick = {
+ showMenu.value = false
+ startCall(CallMediaType.Audio)
+ })
+ }
+ menuItems.add {
+ ItemAction(stringResource(MR.strings.icon_descr_video_call).capitalize(Locale.current), painterResource(MR.images.ic_videocam), onClick = {
+ showMenu.value = false
+ startCall(CallMediaType.Video)
+ })
+ }
+ }
+ menuItems.add {
+ ItemAction(stringResource(MR.strings.search_verb), painterResource(MR.images.ic_search), onClick = {
+ showMenu.value = false
+ showSearch.value = true
+ })
+ }
+ }
+ is ChatInfo.Group -> {
+ // Add members / group link moved to menu
+ if (chatInfo.groupInfo.canAddMembers) {
+ if (!chatInfo.incognito) {
+ menuItems.add {
+ ItemAction(stringResource(MR.strings.icon_descr_add_members), painterResource(MR.images.ic_person_add_500), onClick = {
+ showMenu.value = false
+ addMembers(chatInfo.groupInfo)
+ })
+ }
+ } else {
+ menuItems.add {
+ ItemAction(stringResource(MR.strings.group_link), painterResource(MR.images.ic_add_link), onClick = {
+ showMenu.value = false
+ openGroupLink(chatInfo.groupInfo)
+ })
+ }
+ }
+ }
+ menuItems.add {
+ ItemAction(stringResource(MR.strings.search_verb), painterResource(MR.images.ic_search), onClick = {
+ showMenu.value = false
+ showSearch.value = true
+ })
+ }
+ }
+ else -> {}
+ }
+
val enableNtfs = chatInfo.chatSettings?.enableNtfs
if (((chatInfo is ChatInfo.Direct && chatInfo.contact.ready && chatInfo.contact.active) || chatInfo is ChatInfo.Group) && enableNtfs != null) {
val ntfMode = remember { mutableStateOf(enableNtfs) }
@@ -1242,13 +1325,27 @@ fun BoxScope.ChatInfoToolbar(
}
val oneHandUI = remember { appPrefs.oneHandUI.state }
val chatBottomBar = remember { appPrefs.chatBottomBar.state }
+ val searchTrailingContent: @Composable (() -> Unit)? = if (showContentFilterButton) {{
+ IconButton({ showContentFilterMenu.value = true }) {
+ Icon(
+ painterResource(if (contentFilter.value == null) MR.images.ic_photo_library else MR.images.ic_photo_library_filled),
+ null,
+ Modifier.padding(4.dp),
+ tint = MaterialTheme.colors.primary
+ )
+ }
+ }} else null
+
DefaultAppBar(
navigationButton = { if (appPlatform.isAndroid || showSearch.value) { NavigationButtonBack(onBackClicked) } },
title = { ChatInfoToolbarTitle(chatInfo) },
onTitleClick = if (chatInfo is ChatInfo.Local) null else info,
showSearch = showSearch.value,
+ searchAlwaysVisible = contentFilter.value != null,
onTop = !oneHandUI.value || !chatBottomBar.value,
+ searchPlaceholder = searchPlaceholder,
onSearchValueChanged = onSearchValueChanged,
+ searchTrailingContent = searchTrailingContent,
buttons = { barButtons.forEach { it() } }
)
Box(Modifier.fillMaxWidth().wrapContentSize(Alignment.TopEnd)) {
@@ -1269,6 +1366,65 @@ fun BoxScope.ChatInfoToolbar(
menuItems.forEach { it() }
}
}
+ val contentFilterWidth = remember { mutableStateOf(250.dp) }
+ val contentFilterHeight = remember { mutableStateOf(0.dp) }
+ DefaultDropdownMenu(
+ showContentFilterMenu,
+ modifier = Modifier.onSizeChanged { with(density) {
+ contentFilterWidth.value = it.width.toDp().coerceAtLeast(250.dp)
+ if (oneHandUI.value && chatBottomBar.value && (appPlatform.isDesktop || (platform.androidApiLevel ?: 0) >= 30)) contentFilterHeight.value = it.height.toDp()
+ } },
+ offset = DpOffset(-contentFilterWidth.value, if (oneHandUI.value && chatBottomBar.value) -contentFilterHeight.value else AppBarHeight)
+ ) {
+ val contentFilterMenuItems: List<@Composable () -> Unit> = buildList {
+ availableContent.value.forEach { filter ->
+ val isSelected = contentFilter.value == filter
+ add {
+ ItemAction(
+ stringResource(filter.label),
+ painterResource(if (isSelected) filter.iconFilled else filter.icon),
+ color = if (isSelected) MaterialTheme.colors.primary else Color.Unspecified,
+ onClick = {
+ showContentFilterMenu.value = false
+ if (contentFilter.value == filter) return@ItemAction
+ contentFilter.value = filter
+ showSearch.value = true
+ scope.launch {
+ val c = chatModel.getChat(chatInfo.id)
+ if (c != null) {
+ apiFindMessages(chatsCtx, c, filter.contentTag, "")
+ }
+ }
+ }
+ )
+ }
+ }
+ if (showSearch.value) {
+ add {
+ ItemAction(
+ stringResource(MR.strings.content_filter_all_messages),
+ painterResource(MR.images.ic_forum),
+ onClick = {
+ showContentFilterMenu.value = false
+ contentFilter.value = null
+ showSearch.value = false
+ scope.launch {
+ val c = chatModel.getChat(chatInfo.id)
+ if (c != null) {
+ apiFindMessages(chatsCtx, c, null, "")
+ }
+ }
+ }
+ )
+ }
+ }
+ }
+ if (oneHandUI.value && chatBottomBar.value) {
+ contentFilterMenuItems.asReversed().forEach { it() }
+ } else {
+ contentFilterMenuItems.forEach { it() }
+ }
+ }
}
}
@@ -3425,7 +3581,10 @@ fun PreviewChatLayout() {
developerTools = false,
showViaProxy = false,
showSearch = remember { mutableStateOf(false) },
- showCommandsMenu = remember { mutableStateOf(false) }
+ showCommandsMenu = remember { mutableStateOf(false) },
+ contentFilter = remember { mutableStateOf(null) },
+ availableContent = remember { mutableStateOf(ContentFilter.initialList) },
+ searchPlaceholder = null
)
}
}
@@ -3505,7 +3664,30 @@ fun PreviewGroupChatLayout() {
developerTools = false,
showViaProxy = false,
showSearch = remember { mutableStateOf(false) },
- showCommandsMenu = remember { mutableStateOf(false) }
+ showCommandsMenu = remember { mutableStateOf(false) },
+ contentFilter = remember { mutableStateOf(null) },
+ availableContent = remember { mutableStateOf(ContentFilter.initialList) },
+ searchPlaceholder = null
)
}
}
+
+enum class ContentFilter(
+ val contentTag: MsgContentTag,
+ val label: StringResource,
+ val searchPlaceholder: StringResource,
+ val icon: ImageResource,
+ val iconFilled: ImageResource
+) {
+ Images(MsgContentTag.Image, MR.strings.content_filter_images, MR.strings.placeholder_search_images, MR.images.ic_image, MR.images.ic_image_filled),
+ Videos(MsgContentTag.Video, MR.strings.content_filter_videos, MR.strings.placeholder_search_videos, MR.images.ic_videocam, MR.images.ic_videocam_filled),
+ Voice(MsgContentTag.Voice, MR.strings.content_filter_voice_messages, MR.strings.placeholder_search_voice_messages, MR.images.ic_mic, MR.images.ic_mic_filled),
+ Files(MsgContentTag.File, MR.strings.content_filter_files, MR.strings.placeholder_search_files, MR.images.ic_draft, MR.images.ic_draft_filled),
+ Links(MsgContentTag.Link, MR.strings.content_filter_links, MR.strings.placeholder_search_links, MR.images.ic_link, MR.images.ic_link);
+
+ companion object {
+ val alwaysShow: Set = setOf(MsgContentTag.Image, MsgContentTag.Link)
+
+ val initialList: List = listOf(ContentFilter.Images, ContentFilter.Files, ContentFilter.Links)
+ }
+}
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt
index af4baad90f..eebf4a7bf8 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt
@@ -47,6 +47,7 @@ import kotlin.math.min
const val MAX_NUMBER_OF_MENTIONS = 3
+// Spec: spec/client/compose.md#ComposePreview
@Serializable
sealed class ComposePreview {
@Serializable object NoPreview: ComposePreview()
@@ -92,6 +93,7 @@ object ComposeMessageSerializer : KSerializer {
decoder.decodeLong().let { value -> TextRange(unpackInt1(value), unpackInt2(value)) }
}
+// Spec: spec/client/compose.md#ComposeState
@Serializable
data class ComposeState(
val message: ComposeMessage = ComposeMessage(),
@@ -259,6 +261,7 @@ fun chatItemPreview(chatItem: ChatItem): ComposePreview {
}
}
+// Spec: spec/client/compose.md#AttachmentSelection
@Composable
expect fun AttachmentSelection(
composeState: MutableState,
@@ -341,6 +344,7 @@ suspend fun MutableState.processPickedMedia(uris: List, text:
}
}
+// Spec: spec/client/compose.md#ComposeView
@Composable
fun ComposeView(
rhId: Long?,
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt
index 467f1e52af..4de0175457 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt
@@ -31,6 +31,7 @@ import dev.icerock.moko.resources.compose.painterResource
import kotlinx.coroutines.*
import java.net.URI
+// Spec: spec/client/compose.md#SendMsgView
@Composable
fun SendMsgView(
composeState: MutableState,
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt
index 3f80361249..dd3374d50b 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt
@@ -878,9 +878,11 @@ fun MemberRow(member: GroupMember, user: Boolean = false, infoPage: Boolean = tr
}
fun memberConnStatus(): String {
- return if (member.activeConn?.connDisabled == true) {
- generalGetString(MR.strings.member_info_member_disabled)
+ return if (member.activeConn?.connStatus is ConnStatus.Failed) {
+ generalGetString(MR.strings.member_info_member_failed)
} else if (member.activeConn?.connDisabled == true) {
+ generalGetString(MR.strings.member_info_member_disabled)
+ } else if (member.activeConn?.connInactive == true) {
generalGetString(MR.strings.member_info_member_inactive)
} else {
member.memberStatus.shortText
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 f09d2f44bb..8902a0fd9e 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
@@ -562,6 +562,14 @@ fun GroupMemberInfoLayout(
}
}
+ val connFailedErr = member.activeConn?.connFailedErr
+ if (connFailedErr != null) {
+ SectionDividerSpaced()
+ SectionView {
+ InfoRow(stringResource(MR.strings.info_row_connection_failed), connFailedErr)
+ }
+ }
+
if (groupInfo.membership.memberRole >= GroupMemberRole.Moderator) {
ModeratorDestructiveSection()
} else {
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberSupportView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberSupportView.kt
index e696128288..c3cf954ab6 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberSupportView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberSupportView.kt
@@ -162,7 +162,9 @@ private fun ModalData.MemberSupportViewLayout(
@Composable
fun SupportChatRow(member: GroupMember) {
fun memberStatus(): String {
- return if (member.activeConn?.connDisabled == true) {
+ return if (member.activeConn?.connStatus is ConnStatus.Failed) {
+ generalGetString(MR.strings.member_info_member_failed)
+ } else if (member.activeConn?.connDisabled == true) {
generalGetString(MR.strings.member_info_member_disabled)
} else if (member.activeConn?.connInactive == true) {
generalGetString(MR.strings.member_info_member_inactive)
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt
index 1be2110b1f..064b5370bc 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt
@@ -26,7 +26,7 @@ import chat.simplex.common.ui.theme.DEFAULT_MAX_IMAGE_WIDTH
import chat.simplex.common.views.chat.chatViewScrollState
import chat.simplex.res.MR
import dev.icerock.moko.resources.StringResource
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.*
@Composable
fun CIImageView(
@@ -38,6 +38,7 @@ fun CIImageView(
receiveFile: (Long) -> Unit
) {
val blurred = remember { mutableStateOf(appPrefs.privacyMediaBlurRadius.get() > 0) }
+ val previewBitmap = remember(image) { base64ToBitmap(image) }
@Composable
fun progressIndicator() {
CircularProgressIndicator(
@@ -144,7 +145,7 @@ fun CIImageView(
.privacyBlur(!smallView, blurred, scrollState = chatViewScrollState.collectAsState(), onLongClick = { showMenu.value = true }),
contentAlignment = Alignment.Center
) {
- imageView(base64ToBitmap(image), onClick = {
+ imageView(previewBitmap, onClick = {
if (fileSource != null) {
openFile(fileSource)
}
@@ -178,14 +179,16 @@ fun CIImageView(
Box(
Modifier.layoutId(CHAT_IMAGE_LAYOUT_ID)
+ .then(
+ if (!smallView) {
+ val w = if (previewBitmap.width * 0.97 <= previewBitmap.height) imageViewFullWidth() * 0.75f else DEFAULT_MAX_IMAGE_WIDTH
+ Modifier.width(w).aspectRatio(previewBitmap.width.toFloat() / previewBitmap.height.toFloat())
+ } else Modifier
+ )
.desktopModifyBlurredState(!smallView, blurred, showMenu),
contentAlignment = Alignment.TopEnd
) {
- val res: MutableState?> = remember {
- mutableStateOf(
- if (chatModel.connectedToRemote()) null else runBlocking { imageAndFilePath(file) }
- )
- }
+ val res: MutableState?> = remember { mutableStateOf(null) }
if (chatModel.connectedToRemote()) {
LaunchedEffect(file, CIFile.cachedRemoteFileRequests.toList()) {
withBGApi {
@@ -195,9 +198,9 @@ fun CIImageView(
}
}
} else {
- KeyChangeEffect(file) {
+ LaunchedEffect(file) {
if (res.value == null || res.value!!.third != getLoadedFilePath(file)) {
- res.value = imageAndFilePath(file)
+ res.value = withContext(Dispatchers.IO) { imageAndFilePath(file) }
}
}
}
@@ -206,7 +209,7 @@ fun CIImageView(
val (imageBitmap, data, _) = loaded
SimpleAndAnimatedImageView(data, imageBitmap, file, imageProvider, smallView, @Composable { painter, onClick -> ImageView(painter, image, file.fileSource, onClick) })
} else {
- imageView(base64ToBitmap(image), onClick = {
+ imageView(previewBitmap, onClick = {
if (file != null) {
when {
file.fileStatus is CIFileStatus.RcvInvitation || file.fileStatus is CIFileStatus.RcvAborted ->
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt
index 758980059d..633d6c454e 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt
@@ -61,6 +61,7 @@ data class ChatItemReactionMenuItem (
val onClick: (() -> Unit)?
)
+// Spec: spec/client/chat-view.md#ChatItemView
@Composable
fun ChatItemView(
chatsCtx: ChatModel.ChatsContext,
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt
index f36da6c908..900fa238a5 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt
@@ -144,7 +144,7 @@ fun FramedItemView(
Box(Modifier.fillMaxWidth().weight(1f)) {
ciQuotedMsgView(qi)
}
- val imageBitmap = base64ToBitmap(qi.content.image)
+ val imageBitmap = remember(qi.content.image) { base64ToBitmap(qi.content.image) }
Image(
imageBitmap,
contentDescription = stringResource(MR.strings.image_descr),
@@ -156,7 +156,7 @@ fun FramedItemView(
Box(Modifier.fillMaxWidth().weight(1f)) {
ciQuotedMsgView(qi)
}
- val imageBitmap = base64ToBitmap(qi.content.image)
+ val imageBitmap = remember(qi.content.image) { base64ToBitmap(qi.content.image) }
Image(
imageBitmap,
contentDescription = stringResource(MR.strings.video_descr),
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.kt
index 70d6fa4aa8..8d96102daa 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.kt
@@ -158,7 +158,10 @@ fun ImageFullScreenView(imageProvider: () -> ImageGalleryProvider, close: () ->
val uriDecrypted = remember(media.uri.path) { mutableStateOf(if (media.fileSource?.cryptoArgs == null) media.uri else media.fileSource.decryptedGet()) }
val decrypted = uriDecrypted.value
if (decrypted != null) {
- VideoView(modifier, decrypted, preview, index == settledCurrentPage, close)
+ // settledCurrentPage finishes **only** when fully swiped
+ // So we use pagerState.currentPage that changes right away as the screen is being dragged
+ val isCurrentPage = index == pagerState.currentPage && kotlin.math.abs(pagerState.currentPageOffsetFraction) < 0.3f
+ VideoView(modifier, decrypted, preview, isCurrentPage, close)
DisposableEffect(Unit) {
onDispose { playersToRelease.add(decrypted) }
}
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt
index 60595fc255..3984e5bc40 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt
@@ -153,6 +153,7 @@ fun MarkdownText (
is Format.Italic -> withStyle(ft.format.style) { append(ft.text) }
is Format.StrikeThrough -> withStyle(ft.format.style) { append(ft.text) }
is Format.Snippet -> withStyle(ft.format.style) { append(ft.text) }
+ is Format.Small -> withStyle(ft.format.style) { append(ft.text) }
is Format.Colored -> withStyle(ft.format.style) { append(ft.text) }
is Format.Secret -> {
val ftStyle = ft.format.style
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt
index 77ab62dbf1..293a93b15a 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt
@@ -32,6 +32,7 @@ import chat.simplex.res.MR
import kotlinx.coroutines.*
import kotlinx.datetime.Clock
+// Spec: spec/client/chat-list.md#ChatListNavLinkView
@Composable
fun ChatListNavLinkView(chat: Chat, nextChatSelected: State) {
val showMenu = remember { mutableStateOf(false) }
@@ -228,6 +229,7 @@ suspend fun openChat(
} else {
ChatPagination.Initial(ChatPagination.INITIAL_COUNT)
},
+ contentTag = null,
"",
openAroundItemId
)
@@ -241,11 +243,12 @@ suspend fun openLoadedChat(chat: Chat) {
}
}
-suspend fun apiFindMessages(chatsCtx: ChatModel.ChatsContext, ch: Chat, search: String) {
+suspend fun apiFindMessages(chatsCtx: ChatModel.ChatsContext, ch: Chat, contentTag: MsgContentTag?, search: String) {
withContext(Dispatchers.Main) {
chatsCtx.chatItems.clearAndNotify()
}
- apiLoadMessages(chatsCtx, ch.remoteHostId, ch.chatInfo.chatType, ch.chatInfo.apiId, pagination = if (search.isNotEmpty()) ChatPagination.Last(ChatPagination.INITIAL_COUNT) else ChatPagination.Initial(ChatPagination.INITIAL_COUNT), search = search)
+ val pagination = if (search.isNotEmpty() || contentTag != null) ChatPagination.Last(ChatPagination.INITIAL_COUNT) else ChatPagination.Initial(ChatPagination.INITIAL_COUNT)
+ apiLoadMessages(chatsCtx, ch.remoteHostId, ch.chatInfo.chatType, ch.chatInfo.apiId, pagination, contentTag, search)
}
suspend fun setGroupMembers(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel) = coroutineScope {
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt
index 2109e21bfe..a42f66c6cf 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt
@@ -122,6 +122,7 @@ fun ToggleChatListCard() {
}
}
+// Spec: spec/client/chat-list.md#ChatListView
@Composable
fun ChatListView(chatModel: ChatModel, userPickerState: MutableStateFlow, setPerformLA: (Boolean) -> Unit, stopped: Boolean) {
val oneHandUI = remember { appPrefs.oneHandUI.state }
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt
index 4280845867..9248ac6efe 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt
@@ -35,6 +35,7 @@ import chat.simplex.common.views.chat.item.*
import chat.simplex.res.MR
import dev.icerock.moko.resources.ImageResource
+// Spec: spec/client/chat-list.md#ChatPreviewView
@Composable
fun ChatPreviewView(
chat: Chat,
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/TagListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/TagListView.kt
index 8dfe138da1..c6cc887655 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/TagListView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/TagListView.kt
@@ -43,6 +43,7 @@ import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.*
+// Spec: spec/client/chat-list.md#TagListView
@Composable
fun TagListView(rhId: Long?, chat: Chat? = null, close: () -> Unit, reorderMode: Boolean) {
val userTags = remember { chatModel.userTags }
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt
index ed74e083e7..a02e0dc768 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt
@@ -41,6 +41,7 @@ import kotlinx.coroutines.flow.*
private val USER_PICKER_SECTION_SPACING = 32.dp
+// Spec: spec/client/chat-list.md#UserPicker
@Composable
fun UserPicker(
chatModel: ChatModel,
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt
index 4827e6ae61..300e5f44fe 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt
@@ -74,6 +74,7 @@ object DatabaseUtils {
}
}
+// Spec: spec/database.md#DBMigrationResult
@Serializable
sealed class DBMigrationResult {
@Serializable @SerialName("ok") object OK: DBMigrationResult()
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt
index 1c5f86c8b5..81fac40a40 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt
@@ -29,7 +29,9 @@ fun DefaultAppBar(
onTop: Boolean,
showSearch: Boolean = false,
searchAlwaysVisible: Boolean = false,
+ searchPlaceholder: String? = null,
onSearchValueChanged: (String) -> Unit = {},
+ searchTrailingContent: @Composable (() -> Unit)? = null,
buttons: @Composable RowScope.() -> Unit = {},
) {
// If I just disable clickable modifier when don't need it, it will stop passing clicks to search. Replacing the whole modifier
@@ -78,7 +80,8 @@ fun DefaultAppBar(
AppBar(
title = {
if (showSearch) {
- SearchTextField(Modifier.fillMaxWidth(), alwaysVisible = searchAlwaysVisible, reducedCloseButtonPadding = 12.dp, onValueChange = onSearchValueChanged)
+ val placeholder = searchPlaceholder ?: stringResource(MR.strings.search_verb)
+ SearchTextField(Modifier.fillMaxWidth(), alwaysVisible = searchAlwaysVisible, placeholder = placeholder, trailingContent = searchTrailingContent, reducedCloseButtonPadding = 12.dp, onValueChange = onSearchValueChanged)
} else if (title != null) {
title()
} else if (titleText.value.isNotEmpty() && connection != null) {
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SearchTextField.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SearchTextField.kt
index 7124f34ac0..a122ddd885 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SearchTextField.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SearchTextField.kt
@@ -10,6 +10,7 @@ import androidx.compose.material.TextFieldDefaults.indicatorLine
import androidx.compose.material.TextFieldDefaults.textFieldWithLabelPadding
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
@@ -112,18 +113,26 @@ fun SearchTextField(
placeholder = {
Text(placeholder, style = textStyle.copy(color = MaterialTheme.colors.secondary), maxLines = 1, overflow = TextOverflow.Ellipsis)
},
- trailingIcon = if (searchText.value.text.isNotEmpty()) {{
- IconButton({
- if (alwaysVisible) {
- keyboard?.hide()
- focusManager.clearFocus()
+ trailingIcon = if (searchText.value.text.isNotEmpty() || trailingContent != null) {{
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.offset(x = 8.dp)
+ ) {
+ if (searchText.value.text.isNotEmpty()) {
+ IconButton({
+ if (alwaysVisible) {
+ keyboard?.hide()
+ focusManager.clearFocus()
+ }
+ searchText.value = TextFieldValue("")
+ onValueChange("")
+ }) {
+ Icon(painterResource(MR.images.ic_close), stringResource(MR.strings.icon_descr_close_button), tint = MaterialTheme.colors.primary)
+ }
}
- searchText.value = TextFieldValue("");
- onValueChange("")
- }, Modifier.offset(x = reducedCloseButtonPadding)) {
- Icon(painterResource(MR.images.ic_close), stringResource(MR.strings.icon_descr_close_button), tint = MaterialTheme.colors.primary,)
+ trailingContent?.invoke()
}
- }} else trailingContent,
+ }} else null,
singleLine = true,
enabled = enabled,
interactionSource = interactionSource,
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt
index db1a0be9da..c4821d1a20 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt
@@ -114,6 +114,7 @@ fun annotatedStringResource(id: StringResource, vararg args: Any?): AnnotatedStr
expect fun SetupClipboardListener()
// maximum image file size to be auto-accepted
+// Spec: spec/services/files.md#MAX_IMAGE_SIZE
const val MAX_IMAGE_SIZE: Long = 261_120 // 255KB
const val MAX_IMAGE_SIZE_AUTO_RCV: Long = MAX_IMAGE_SIZE * 2
const val MAX_VOICE_SIZE_AUTO_RCV: Long = MAX_IMAGE_SIZE * 2
@@ -129,6 +130,8 @@ const val MAX_FILE_SIZE_LOCAL: Long = Long.MAX_VALUE
expect fun getAppFileUri(fileName: String): URI
+expect fun clearImageCaches()
+
// https://developer.android.com/training/data-storage/shared/documents-files#bitmap
expect suspend fun getLoadedImage(file: CIFile?): Pair?
@@ -422,6 +425,7 @@ fun deleteAppFiles() {
} catch (e: java.lang.Exception) {
Log.e(TAG, "Util deleteAppFiles error: ${e.stackTraceToString()}")
}
+ clearImageCaches()
}
fun directoryFileCountAndSize(dir: String): Pair { // count, size in bytes
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml
index 41f454b2dd..86e51e6937 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml
@@ -2522,4 +2522,9 @@
البصمة في عنوان الخادم لا تتطابق مع الشهادة: %1$s.
لا اشتراك
أنت غير متصل بالخادم المستخدم لاستقبال الرسائل من هذا الاتصال (لا يوجد اشتراك).
+ احذف رسائل العضو
+ حذف رسائل العضو؟
+ احذف الرسائل
+ ستُحذف رسائل العضو - ولا يمكن التراجع عن ذلك!
+ أزل واحذف الرسائل
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 371f0e076f..8b1eb44249 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
@@ -368,6 +368,18 @@
Edit
Info
Search
+ Search images
+ Search videos
+ Search voice messages
+ Search files
+ Search links
+ Images
+ Videos
+ Voice messages
+ Files
+ Links
+ All messages
+ Filter
Archive
Archive report
Archive reports
@@ -1894,6 +1906,7 @@
Blocked by admin
blocked
disabled
+ failed
inactive
MEMBER
Role
@@ -1912,6 +1925,7 @@
Group
Chat
Connection
+ Connection failed
direct
indirect (%1$s)
Message queue info
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml
index a3b97f0be2..5c8f73bf93 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml
@@ -1555,12 +1555,12 @@
Obrint la base de dades…
Comproveu que l\'enllaç SimpleX sigui correcte.
l\'enviament de fitxers encara no està suportat
- Intentant connectar-se al servidor utilitzat per rebre missatges d\'aquest contacte.
+ Intentant connectar-se al servidor utilitzat per rebre missatges d\'aquesta connexió.
S\'està provant de connectar-se al servidor utilitzat per rebre missatges d\'aquest contacte (error: %1$s).
Usar perfil actual
Usar nou perfil incògnit
Error aplicació
- Esteu connectat al servidor utilitzat per rebre missatges d\'aquest contacte.
+ Esteu connectat al servidor utilitzat per rebre missatges d\'aquesta connexió.
El teu perfil s\'enviarà al contacte del qual has rebut aquest enllaç.
Heu compartit una ruta de fitxer no vàlida. Informeu-ne als desenvolupadors de l\'aplicació.
Us connectareu amb tots els membres del grup.
@@ -2501,4 +2501,11 @@
L\'empremta digital a l\'adreça del servidor de destinació no coincideix amb el certificat: %1$s.
L\'empremta digital a l\'adreça del servidor de reenviament no coincideix amb el certificat: %1$s.
L\'empremta digital a l\'adreça del servidor no coincideix amb el certificat: %1$s.
+ cap subscripció
+ No esteu connectat(da) al servidor que s\'utilitza per rebre missatges d\'aquesta connexió (sense subscripció).
+ Suprimir missatges de membre
+ Suprimir missatges de membre?
+ Suprimir missatges
+ Els missatges de membre s\'eliminaran; això no es pot desfer!
+ Eliminar membre i els seus missatges
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/da/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/da/strings.xml
index 30e557a4e3..38507cc228 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/da/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/da/strings.xml
@@ -859,4 +859,6 @@
Tilslutning af opkald …
Tilslutning af opkald
Tilslutning (introduceret)
+ Overfør fra en anden enhed på den nye enhed og scan QR-koden.]]>
+ Overfør fra en anden enhed
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml
index e038207801..3254d0a63f 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml
@@ -2612,4 +2612,21 @@
Fingerabdruck in der Serveradresse stimmt nicht mit dem Zertifikat überein: %1$s.
Kein Abonnement
Sie sind nicht mit dem Server verbunden, der für den Empfang von Nachrichten dieser Verbindung genutzt wird (kein Abonnement).
+ Mitgliedsnachrichten löschen
+ Mitgliedsnachrichten löschen?
+ Mitgliedsnachrichten löschen
+ Mitgliedsnachrichten werden gelöscht. Dies kann nicht rückgängig gemacht werden!
+ Mitglied entfernen und Nachrichten löschen
+ Alle Nachrichten
+ Dateien
+ Filter
+ Bilder
+ Links
+ Dateien suchen
+ Bilder suchen
+ Links suchen
+ Videos suchen
+ Sprachnachrichten suchen
+ Videos
+ Sprachnachrichten
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml
index 9e38019c8b..6f326c33c8 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml
@@ -1,39 +1,39 @@
- 1 μέρα
+ 1 ημέρα
1 μήνας
Για το SimpleX
- Σαρώστε τον QR κωδικό
+ Σάρωσε τον QR κωδικό
α + β
Για το SimpleX Chat
- Σαρώστε τον κωδικό ασφαλείας από την εφαρμογή επαφών σας
+ Σάρωσε τον κωδικό ασφαλείας από την εφαρμογή επαφών σου
Ασφαλή ουρά
δε
Κωδικός ασφαλείας
- Σαρώστε τον κωδικό QR διακομιστή
+ Σάρωσε τον QR κωδικό του διακομιστή
μυστικό
1 εβδομάδα
αξιολόγηση ασφαλείας
- Συναινώ
+ Επέτρεψε
Αποδοχή
Αποδοχή ανώνυμης περιήγησης
Προσθήκη προκαθορισμένου διακομιστή
Προσθήκη σε άλλη συσκευή
- Όλες οι επαφές σας θα παραμείνουν ενεργές.
+ Όλες οι επαφές σου θα παραμείνουν ενεργές.
Αποδοχή
διαχειριστής
- Προσθέστε μήνυμα καλωσορίσματος
+ Πρόσθεσε μήνυμα καλωσορίσματος
Όλα τα μέλη της ομάδας θα παραμήνουν συνδεδεμένα.
Προσθήκη προφίλ
- Προφορά
+ Χρώμα έμφασης
πάντα
Αποδοχή
- Επιτρέψτε τα μηνύματα που εξαφανίζονται μόνο εάν το επιτρέπει η επαφή σας.
- Επιτρέψτε στις επαφές σας να διαγράφουν μη αναστρέψιμα τα απεσταλμένα μηνύματα. (24 ώρες)
- Επιτρέψτε στις επαφές σας να στέλνουν μηνύματα που εξαφανίζονται.
- Επιτρέπονται τα φωνητικά μηνύματα μόνο εάν τα επιτρέπει η επαφή σας.
- Επιτρέψτε στις επαφές σας να σας καλέσουν.
- Επιτρέψτε στις επαφές σας να στέλνουν φωνητικά μηνύματα.
+ Επιτρέψτε τα μηνύματα που εξαφανίζονται μόνο εάν το επιτρέπει η επαφή σου.
+ Επέτρεψε στις επαφές σου να διαγράφουν μη αναστρέψιμα τα απεσταλμένα μηνύματα. (24 ώρες)
+ Επέτρεψε στις επαφές σου να στέλνουν μηνύματα που εξαφανίζονται.
+ Επέτρεψε τα φωνητικά μηνύματα μόνο εάν τα επιτρέπει η επαφή σου.
+ Επέτρεψε στις επαφές σου να σε καλέσουν.
+ Επέτρεψε στις επαφές σου να στέλνουν φωνητικά μηνύματα.
Να επιτρέπεται η αποστολή άμεσων μηνυμάτων στα μέλη.
Επιτρέπεται η αποστολή μηνυμάτων που εξαφανίζονται.
Επιτρέπεται η αποστολή φωνητικών μηνυμάτων.
@@ -41,70 +41,69 @@
Αποδοχή
Αποδοχή αιτήματος σύνδεσης;
αποδεκτή κλήση
- Πρόσβαση στους διακομιστές μέσω SOCKS proxy στην πόρτα %d; Ο διακομιστής μεσολάβησης (proxy server) πρέπει να είναι ενεργός πριν ενεργοποιηθεί αυτή η ρύθμιση.
+ Πρόσβαση στους διακομιστές μέσω διαμομιστή μεσολάβησης SOCKS στη θύρα %d; Ο διακομιστής μεσολάβησης (proxy server) πρέπει να είναι ενεργός πριν ενεργοποιηθεί αυτή η ρύθμιση.
Προσθήκη διακομιστή
Προχωρημένες ρυθμίσεις δικτύου
Προσθήκη διακομιστών μέσω σάρωσης QR κωδικών.
Οι διαχειριστές μπορούν να δημιουργήσουν τους συνδέσμους συμμετοχής σε ομάδες.
Όλες οι συνομιλίες και τα μηνύματα θα διαγραφούν - αυτή η ενέργεια δεν μπορεί να αντιστραφεί!
Όλα τα μηνύματα θα διαγραφούν - αυτή η ενέργεια δεν μπορεί να αντιστραφεί! Τα μηνύματα θα διαγραφούν ΜΟΝΟ για εσάς.
- Επιτρέψτε τη μη αναστρέψιμη διαγραφή μηνυμάτων μόνο εάν το επιτρέπει η επαφή σας. (24 ώρες)
- Επιτρέπονται οι κλήσεις μόνο εάν η επαφή σας τις επιτρέπει.
+ Επέτρεψε τη μη αναστρέψιμη διαγραφή μηνυμάτων μόνο εάν το επιτρέπει η επαφή σου. (24 ώρες)
+ Επιτρέπονται οι κλήσεις μόνο εάν η επαφή σου τις επιτρέπει.
Επιτρέψτε τη μη αναστρέψιμη διαγραφή των απεσταλμένων μηνυμάτων. (24 ώρες)
Να επιτρέπονται τα φωνητικά μηνύματα;
Πάντα ενεργό
Να χρησιμοποιείται πάντα αναμεταδότη
- Αναζήτηση
+ Αναζήτησε
Ανενεργό
- "Το προφίλ σας %1$s θα μοιραστεί"
- Η SimpleX διεύθυνση σας
+ Το προφίλ σου %1$s θα διαμοιραστεί
+ Η SimpleX διεύθυνση σου
Αντίγραφο δεδομένων εφαρμογής
5 λεπτά
- Θα συνδεθείτε όταν η συσκευή της επαφής σας είναι συνδεμένει, παρακαλώ περιμένετε ή ελέγξτε αργότερα!
- Ο ICE διακομιστής σας
+ Θα συνδεθείς όταν η συσκευή της επαφής σου είναι συνδεδεμένη, παρακαλώ περίμενε ή έλεγξε αργότερα!
+ Ο ICE διακομιστής σου
Έκδοση εφαρμογής
- Στείλατε πρόσκληση ομάδας
+ Έστειλες πρόσκληση ομάδας
1 λεπτό
- Ο διακομιστής σας
+ Ο διακομιστής σου
Διεύθυνση
Ακύρωση
Πίσω
30 δευτερόλεπτα
- Θα συνδεθείτε με όλα τα μέλη της ομάδας.
+ Θα συνδεθείς με όλα τα μέλη της ομάδας.
%1$s θέλει να συνδεθεί μαζί σου μέσω
- Επιτρέπονται αντιδράσεις μηνύματος.
- Ο διακομιστής XFTP σας
- Η διεύθυνση του διακομιστή σας
- Το προφίλ, επαφές και παραδομένα μηνύματα σας είναι αποθηκευμένα στην συσκευή σας.
- Ο διακομιστής SMP σας
+ Επέτρεψε τις αντιδράσεις σε μηνύματα.
+ Ο διακομιστής XFTP σου
+ Η διεύθυνση του διακομιστή σου
+ Το προφίλ, επαφές και παραδομένα μηνύματα σου είναι αποθηκευμένα στην συσκευή σου.
+ Ο διακομιστής SMP σου
Επιτρέπεται να σταλούν αρχεία και μέσα.
- Το τυχαίο προφίλ σας
+ Το τυχαίο προφίλ σου
%1$s ΜΕΛΗ
- Επιτρέπεται
- Οι προτιμήσεις σας
+ Αποδοχή
+ Οι προτιμήσεις σου
Συντάκτης
- Ο ΙCE διακομιστής σας
- Κωδικός εφαρμογής
+ Ο ΙCE διακομιστής σου
+ Κωδικός πρόσβασης εφαρμογής
Αίτημα σύνδεσης θα σταλεί σε αυτό το μέλος της ομάδας.
ΟΙΚΟΝΑ ΕΦΑΡΜΟΓΗΣ
Εφαρμογή
- Οι ρυθμίσεις σας
+ Οι ρυθμίσεις σου
Έκδοση εφαρμογής: v%s
σφάλμα κλήσης
- "ακυρώθηκε %s"
+ ακυρώθηκε %s
ακύρωση πρόβλεψη συνδέσμου
- Αλλαγή κωδικού πρόσβασης βάση δεδομένων?
+ Αλλαγή φράσης πρόσβασης της βάσης δεδομένων?
Αλλαγή κωδικού πρόσβασης
Αλλαγή ρόλου του %s σε %s
Φόντο
Δεν είναι δυνατή η προετοιμασία της βάσης δεδομένων
- Ένα νέο τυχαίο προφίλ θα μοιραστεί.
+ Ένα νέο τυχαίο προφίλ θα διαμοιραστεί.
Δεν είναι δυνατή η πρόσκληση επαφών!
Αλλαγή διεύθυνσης λήψης
Πιστοποίηση μη διαθέσιμη
- Αλλαγή
- "
-\nΔιαθέσιμο στην έκδοση 5.1"
+ Άλλαξε
+ \nΔιαθέσιμο στην έκδοση 5.1
Τέλος κλήσης
ΚΛΗΣΕΙΣ
Αυτόματη αποδοχή
@@ -126,25 +125,25 @@
Όλα τα δεδομένα της εφαρμογής διαγράφηκαν.
Εμφάνιση
Τέλος κλήσης %1$s
- Ακύρωση
+ Ακύρωσε
Αλλαγή διεύθυνσης λήψης;
αλλαγή διεύθυνσης…
Αλλαγή λειτουργίας αυτοκαταστροφής
Κάμερα
κλήση σε εξέλιξη
Αυτόματη αποδοχή εικόνων
- Αλλαγή του ρόλου σας σε %s
+ Αλλαγή του ρόλου σου σε %s
Κλήση σε εξέλιξη
Πιστοποίηση απέτυχε
- "σύνδεση %1$d"
+ σύνδεση %1$d
Δημιουργία διεύθυνση SimpleX
- επαφή έχει κρυπτογράφηση από άκρο σε άκρο
+ η επαφή έχει κρυπτογράφηση από άκρη-σε-άκρη
Δημιουργία μια ομάδας χρησιμοποιώντας ένα τυχαίο προφίλ.
Δημιουργία ομάδας
Δημιουργία προφίλ
Η επαφή και όλα τα μηνύματα θα διαγραφούν - αυτό δεν μπορεί να αναιρεθεί!
Δημιουργία προφίλ
- Οι επαφές μπορούν να επισημάνουν μηνύματα προς διαγραφή; θα μπορείτε να τα δείτε.
+ Οι επαφές μπορούν να επισημάνουν μηνύματα προς διαγραφή, τα οποία θα μπορείς να τα δεις.
Σύνδεση μέσω μιας εφάπαξ σύνδεσης;
Δημιουργία σύνδεσμου
Σύνδεση μέσω σύνδεσμο/κωδικό γρήγορης ανταπόκρισης
@@ -153,11 +152,11 @@
συνδέεται…
Όνομα επαφής
Δημιουργία διεύθυνσης
- Αντιγραφή
+ Αντέγραψε
Συνέχεια
Σύνδεση μέσω σύνδεσμο;
Επαφή υπάρχει ήδη
- Σύνδεση στον εαυτό σας;
+ Σύνδεση στον εαυτό σου;
Δημιουργία μυστικής ομάδας
Συνδεδεμένη σε επιφάνεια εργασίας
δημιουργός
@@ -180,8 +179,8 @@
Συνδε
Σύνδεση ανώνυμης περιήγησης
σύνδεση επετεύχθη
- επαφή δεν έχει κρυπτογράφηση από άκρο σε άκρο
- Επαφή επιτρέπει
+ η επαφή δεν έχει κρυπτογράφηση από άκρη-σε-άκρη
+ Η επαφή επιτρέπει
σύνδεση (ανακοινώθηκε)
Συνδεδεμένος
συνδέεται…
@@ -194,29 +193,29 @@
Δημιουργία μυστικής ομάδας
Σφάλμα σύνδεσης
Η επαφή δεν είναι συνδράμει αυτή τη στιγμή!
- Συνδεδεμένο απευθείας
- Η ιδιωτικότητά σας
- Η επαφή σας έστειλε ένα αρχείο το οποίο είναι μεγαλύτερο από το παρόν υποστηριζόμενο μέγεθος (%1$s).
- Το προφίλ της συνομιλίας σας θα σταλεί στην επαφή σας
+ αιτούμενη σύνδεση
+ Η ιδιωτικότητά σου
+ Η επαφή σου έστειλε ένα αρχείο το οποίο είναι μεγαλύτερο από το παρόν υποστηριζόμενο μέγεθος (%1$s).
+ Το προφίλ της συνομιλίας σου θα σταλεί\nστην επαφή σου
Χρήση νέου ανώνυμου προφίλ
Ήδη συνδέεται
- Απορρίψατε την πρόσκληση της ομάδας
+ Απέρριψες την πρόσκληση της ομάδας
Σύνδεση μέσω διεύθυνση επαφής
- Χρήση του τρέχων προφίλ
- Σύνδεση
- Το τρέχον προφίλ σας
- αφαιρέσατε %1$s
- "Συμμετοχή ομάδας;"
- Οι επαφες σας θα παραμένουν συνδεδεμένες.
- Προσπάθεια σύνδεσης με τον διακομιστή που χρησιμοποιείται για τη λήψη μηνυμάτων από αυτήν την επαφή.
+ Χρήση του τρέχοντος προφίλ
+ Συνδέσου
+ Το τρέχον προφίλ σου
+ αφαίρεσες %1$s
+ Συμμετοχή ομάδας;
+ Οι επαφες σου θα παραμένουν συνδεδεμένες.
+ Προσπάθεια σύνδεσης με τον διακομιστή που χρησιμοποιείται για τη λήψη μηνυμάτων από αυτήν τη σύνδεση.
Το προφίλ σου θα σταλεί στην επαφή από την οποία έλαβες αυτόν τον σύνδεσμο.
σφάλμα
Άνοιγμα βάση δεδομένων…
Η προβολή συνετρίβη
συνδεδεμένο
- Κοινοποιήσατε μια μη έγκυρη διαδρομή αρχείου. Αναφέρετε το πρόβλημα στους προγραμματιστές της εφαρμογής.
+ Κοινοποίησες μία μη έγκυρη διαδρομή αρχείου. Ανέφερε το πρόβλημα στους προγραμματιστές της εφαρμογής.
Μη έγκυρη διαδρομή αρχείου
- Είστε συνδεδεμένοι στον διακομιστή που χρησιμοποιείται για τη λήψη μηνυμάτων από αυτήν την επαφή.
+ Είσαι συνδεδεμένος στον διακομιστή που χρησιμοποιείται για τη λήψη μηνυμάτων από αυτήν τη σύνδεση.
%d μύνημα επισημάνθηκε ως διαγραμμένο
%1$d μυνήματα συντονίζονται από %2$s
επισημάνθηκε ως διαγραμμένο
@@ -230,22 +229,22 @@
Η αλλαγή διεύθυνσης θα ακυρωθεί. Θα χρησιμοποιηθεί η παλιά διεύθυνση παραλαβής.
Ενεργές συνδέσεις
Προχωρημένες ρυθμίσεις
- Πρόσθετος τόνος
+ Επιπρόσθετο χρώμα έμφασης
Προσθήκη επαφής
- Διακοπή αλλαγής διεύθυνσης
+ Ακύρωση αλλαγής διεύθυνσης
Προχωρημένες ρυθμίσεις
Οι διαχειριστές μπορούν να αποκλείσουν ένα μέλος για όλους.
Αναγνωρισμένο
παραπάνω, λοιπόν:
- Προσθέστε τη διεύθυνση στο προφίλ σας, έτσι ώστε οι επαφές σας να μπορούν να τη μοιραστούν με άλλα άτομα. Το ενημέρωμένο προφίλ θα σταλεί στις επαφές σας.
+ Πρόσθεσε τη διεύθυνση στο προφίλ σου, έτσι ώστε οι επαφές σου να μπορούν να τη διαμοιραστούν με άλλα άτομα. Το ενημέρωμένο σου προφίλ θα αποσταλεί στις επαφές σου.
διαχειριστές
Λάθη αναγνώρισης
- Προειδοποίηση: το αρχείο θα διαγραφεί.]]>
+ Προειδοποίηση: το αρχείο αρχειοθέτησης θα διαγραφεί.]]>
Υπέρβαση χωρητικότητας - ο παραλήπτης δεν έλαβε μηνύματα που στάλθηκαν προηγουμένως.
αποκλεισμένος από τον διαχειριστή
Συνομιλίες
όλα τα μέλη
- Όλες οι επαφές σας θα παραμείνουν ενεργές. Το ανανεωμένο προφίλ σας θα αποσταλεί στις επαφές σας.
+ Όλες οι επαφές σου θα παραμείνουν ενεργές. Το ανανεωμένο προφίλ σου θα αποσταλεί στις επαφές σου.
Να χρησιμοποιείται πάντα ιδιωτική δρομολόγηση.
Ένα κενό προφίλ συνομιλίας με το παρεχόμενο όνομα δημιουργείται και η εφαρμογή ανοίγει ως συνήθως.
Η βάση δεδομένων της συνομιλίας διαγράφηκε
@@ -263,7 +262,7 @@
Η εφαρμογή κρυπτογραφεί νέα τοπικά αρχεία (εκτός απο βίντεο).
Καλύτερες ομάδες
Γίνεται ήδη συμμετοχή στην ομάδα!
- Αρχειοθέτηση και αποστολή
+ Αρχειοθέτηση και ανέβασμα
%1$d διαφορετικό/κα σφάλμα/τα αρχείου/ων.
Η υπηρεσία παρασκηνίου λειτουργεί πάντα - οι ειδοποιήσεις θα εμφανίζονται μόλις τα μηνύματα είναι διαθέσιμα.
%1$d αρχείο/α ακόμα κατεβαίνουν.
@@ -272,10 +271,10 @@
%1$d αρχείο/α δεν κατέβηκε/καν.
%1$s μήνυμα/τα δεν προωθήθηκε/καν
Προφίλ συνομιλίας
- για κάθε προφίλ συνομιλίας που έχετε στην εφαρμογή.]]>
- Παρακαλώ σημειώστε: οι αναμεταδότες μηνυμάτων και αρχείων συνδέονται μέσω διακομιστή μεσολάβησης SOCKS. Οι κλήσεις και οι προεπισκοπήσεις συνδέσμων αποστολής χρησιμοποιούν άμεση σύνδεση.]]>
+ για κάθε προφίλ συνομιλίας που έχεις στην εφαρμογή.]]>
+ Παρακαλώ σημείωσε: οι αναμεταδότες μηνυμάτων και αρχείων συνδέονται μέσω διακομιστή μεσολάβησης SOCKS. Οι κλήσεις και οι προεπισκοπήσεις συνδέσμων αποστολής χρησιμοποιούν άμεση σύνδεση.]]>
Πάντα
- Η ενημέρωση της εφαρμογής κατεβαίνει
+ Η ενημέρωση της εφαρμογής κατέβηκε
Έλεγχος για ενημερώσεις
Οποιοσδήποτε μπορεί να φιλοξενήσει διακομιστές.
κλήση ήχου (χωρίς κρυπτογράφηση e2e)
@@ -291,21 +290,21 @@
Χρώματα συνομιλίας
ΒΑΣΗ ΔΕΔΟΜΕΝΩΝ ΣΥΝΟΜΙΛΙΑΣ
Η συνομιλία εκτελείται
- Παρακαλώ σημειώστε: ΔΕΝ θα μπορείτε να ανακτήσετε ή να αλλάξετε τη φράση πρόσβασης εάν τη χάσετε.]]>
+ Παρακαλώ σημείωσε: ΔΕΝ θα μπορείς να ανακτήσεις ή να αλλάξεις τη φράση πρόσβασης εάν τη χάσεις.]]>
Αποκλεισμός για όλους
- Και εσείς και η επαφή σας μπορείτε να προσθέστε αντιδράσεις μηνυμάτων.
- Και εσείς και η επαφή σας μπορείτε να κάνετε κλήσεις.
+ Και εσύ και η επαφή σου μπορείτε να προσθέστε αντιδράσεις μηνυμάτων.
+ Και εσύ και η επαφή σου μπορείτε να κάνετε κλήσεις.
Επιτρέψτε την αποστολή συνδέσμων SimpleX.
Αραβικά, Βουλγαρικά, Φινλανδικά, Εβραϊκά, Ταϊλανδέζικα και Ουκρανικά - χάρη στους χρήστες και το Weblate.
Μεταφορά δεδομένων εφαρμογής
Θάμπωμα για καλύτερη ιδιωτικότητα.
Η συνομιλία έχει μεταφερθεί!
Αρχειοθέτηση της βάσης δεδομένων
- Όλες οι επαφές, συζητήσεις και αρχεία θα κρυπτογραφηθούν με ασφάλεια και θα μεταφορτωθούν σε διαμορφωμένα κομμάτια αναμετάδοσης XFTP.
+ Όλες οι επαφές, οι συζητήσεις και τα αρχεία θα κρυπτογραφηθούν με ασφάλεια και θα μεταφορτωθούν τμηματικά σε διαμορφωμένους αναμεταδότες XFTP.
Κινητή τηλεφωνία
- Δημιουργία ομάδας : για την δημιουργίας νέας ομάδας.]]>
- Ελέγξτε τη σύνδεσή σας στο διαδίκτυο και δοκιμάστε ξανά
- Συζήτηση με τους προγραμματιστές
+ Δημιουργία ομάδας : για να δημιουργήσεις μία νέα ομάδα.]]>
+ Έλεγξε τη σύνδεσή σου στο διαδίκτυο και δοκίμασε ξανά
+ Συνομίλησε με τους προγραμματιστές
Ζήτησε να λάβει το βίντεο
Δεν είναι δυνατή η αποστολή μηνυμάτων στο μέλος της ομάδας
Αλλαγή λειτουργίας κλειδώματος
@@ -313,16 +312,16 @@
άλλαξε η διεύθυνση για εσάς
και %d άλλες εκδηλώσεις
Μαύρο
- Πρόσθετο δευτερεύον
- Και εσείς και η επαφή σας μπορείτε να διαγράψετε απεσταλμένα μηνύματα χωρίς ανατροπή. (24 ώρες)
- Και εσείς και η επαφή σας μπορείτε να στείλετε ηχητικά μηνύματα.
+ Επιπρόσθετο δευτερεύων
+ Και εσύ και η επαφή σου μπορείτε να διαγράψετε απεσταλμένα μηνύματα χωρίς ανατροπή. (24 ώρες)
+ Και εσύ και η επαφή σου μπορείτε να στείλετε ηχητικά μηνύματα.
Η συνομιλία σταμάτησε
- Η συνομιλία έχει διακοπεί. Εάν χρησιμοποιήσατε ήδη αυτήν τη βάση δεδομένων σε άλλη συσκευή, θα πρέπει να τη μεταφέρετε πίσω προτού ξεκινήσετε τη συνομιλία.
- Η λειτουργία βελτιστοποίησης της μπαταρίας είναι ενεργή, η υπηρεσία παρασκηνίου και τα περιοδικά αιτήματα για νέα μηνύματα θα απενεργοποιηθούν. Μπορείτε να τα ενεργοποιήσετε ξανά μέσω των ρυθμίσεων.
- σύνδεσμος μιας χρήσης
+ Η συνομιλία έχει διακοπεί. Εάν ήδη χρησιμοποίησες αυτήν τη βάση δεδομένων σε άλλη συσκευή, θα πρέπει να τη μεταφέρεις πίσω προτού ξεκινήσεις τη συνομιλία.
+ Η λειτουργία βελτιστοποίησης της μπαταρίας είναι ενεργή, η υπηρεσία παρασκηνίου και τα περιοδικά αιτήματα για νέα μηνύματα θα απενεργοποιηθούν. Μπορείς να τα ενεργοποιήσεις ξανά μέσω των ρυθμίσεων.
+ σύνδεσμος 1-χρήσης
Κλήσεις ήχου & βίντεο
Κλήσεις ήχου/βίντεο
- Κωδικός εφαρμογής
+ Κωδικός πρόσβασης εφαρμογής
Συνεδρία εφαρμογής
Η συνομιλία σταμάτησε
Έλεγχος για ενημερώσεις
@@ -331,47 +330,47 @@
Bluetooth
έντονο
Κονσόλα συνομιλίας
- Παρακαλώ σημειώστε: η χρήση της ίδιας βάσης δεδομένων σε δύο συσκευές θα διακόψει την αποκρυπτογράφηση των μηνυμάτων από τις συνδέσεις σας, ως προστασία ασφαλείας.]]>
+ Παρακαλώ σημείωσε: η χρήση της ίδιας βάσης δεδομένων σε δύο συσκευές θα διακόψει την αποκρυπτογράφηση των μηνυμάτων από τις συνδέσεις σου, ως προστασία ασφαλείας.]]>
Χρησιμοποιεί περισσότερη μπαταρία! Η εφαρμογή εκτελείται πάντα στο παρασκήνιο - οι ειδοποιήσεις εμφανίζονται αμέσως.]]>
Η βάση δεδομένων της συνομιλίας εξάχθηκε
κλήση
Κακή διεύθυνση Desktop
- Μεταφορά απο άλλη συσκευή στη νέα συσκευή και σαρώστε τον κωδικό QR.]]>
+ Μεταφορά από άλλη συσκευή στη νέα συσκευή και σάρωσε τον κωδικό QR.]]>
Με προφίλ συνομιλίας (προεπιλογή) ή μέσω σύνδεσης (BETA).
Κάμερα και μικρόφωνο
6 νέες γλώσσες διεπαφής
- Καλό για την μπαταρία. Η εφαρμογή ελέγχει για την παραλαβή μηνυμάτων κάθε 10 λεπτά. Ενδέχεται να χάσετε κλήσεις ή επείγοντα μηνύματα.]]>
+ Καλό για την μπαταρία. Η εφαρμογή ελέγχει για την παραλαβή μηνυμάτων κάθε 10 λεπτά. Ενδέχεται να χάσεις κλήσεις ή επείγοντα μηνύματα.]]>
Επισύναψη
- Διακοπή αλλαγής διεύθυνσης;
+ Ακύρωση αλλαγής διεύθυνσης;
Επιλέξτε ένα αρχείο
Όλα τα νέα μηνύνματα απο %s θα αποκρυφθούν!
Δεν είναι δυνατή η λήψη του αρχείου
Πιστοποίηση
Όλα τα μηνύματα θα διαγραφούν - αυτή η ενέργεια δεν μπορεί να αντιστραφεί!
- Ελέγχει νέα μηνύματα κάθε 10 λεπτά για έως και 1 λεπτό
+ Ελέγχει νέα μηνύματα κάθε 10 λεπτά για εώς και 1 λεπτό
Η εφαρμογή μπορεί να λαμβάνει ειδοποιήσεις μόνο όταν εκτελείται, καμία υπηρεσία δεν θα ξεκινήσει στο παρασκήνιο
Μπορεί να απενεργοποιηθεί μέσω των ρυθμίσεων – οι ειδοποιήσεις θα εξακολουθούν να εμφανίζονται ενώ η εφαρμογή εκτελείται.]]>
- Επιτρέψτε τις επαφές σας να χρησιμοποιούν αντιδράσεις μηνυμάτων.
- Και εσείς και η επαφή σας μπορείτε να στείλετε μηνύματα που εξαφανίζονται.
+ Επέτρεψε στις επαφές σου να χρησιμοποιούν αντιδράσεις μηνυμάτων.
+ Και εσύ και η επαφή σου μπορείτε να στείλετε μηνύματα που εξαφανίζονται.
Κάμερα μη διαθέσιμη
Ελέγξτε την διεύθυνση του διακομιστή και δοκιμάστε ξανά.
- Επιτρέψτε αντιδράσεις μηνυμάτων εφόσον οι επαφές σας το επιτρέπουν.
+ Επέτρεψε αντιδράσεις μηνυμάτων εφόσον οι επαφές σου το επιτρέπουν.
%1$d μήνυμα/τα παραλήφθηκε/καν.
Κλήσεις απογορευμένες!
Δεν είναι δυνατή η αποστολή μηνύματος
Η κλήση έχει ήδη τερματιστεί!
Ο κωδικός πρόσβασης της εφαρμογής αντικαθίσταται με κωδικό πρόσβασης αυτοκαταστροφής.
Το Android Keystore θα χρησιμοποιηθεί για την ασφαλή αποθήκευση της φράσης πρόσβασης μετά την επανεκκίνηση της εφαρμογής ή την αλλαγή της φράσης πρόσβασης - θα επιτρέπει τη λήψη ειδοποιήσεων.
- Δεν είναι δυνατή η πρόσβαση στο Keystore για αποθήκευση του κωδικού πρόσβασης της βάσης δεδομένων
+ Δεν είναι δυνατή η πρόσβαση στο Keystore για αποθήκευση του κωδικού της βάσης δεδομένων
Αποκλεισμός μέλους
Αποκλεισμός μέλους;
Προτιμήσεις συνομιλίας
Καλύτερα μηνύματα
Εφαρμογή
- Συνέναιση υποβάθμισης
+ Συναίνεση υποβάθμισης
Κάμερα
κλήση ήχου
- Αρχειοθετήστε τις επαφές για να συνομιλήσετε αργότερα.
+ Αρχειοθέτησε τις επαφές για να συνομιλήσεις αργότερα.
Όλα τα προφίλ
%1$d μηνύμα/τα παραλείφθηκε/καν
κακό μήνυμα hash
@@ -380,7 +379,7 @@
Κακό αναγνωριστικό μηνύματος
ΣΥΝΟΜΙΛΙΕΣ
Η βάση δεδεδομένων της συνομιλίας εισάχθηκε
- "συμφωνία κρυπτογράφησης για %s…"
+ συμφωνία κρυπτογράφησης για %s…
Να επιτραπούν οι κλήσεις;
Αποκλεισμός μέλους για όλους;
Κλήσεις ήχου και βίντεο
@@ -390,16 +389,16 @@
Καλύτερη εμπειρία χρήστη
Δεν είναι δυνατή η κλήση μέλους ομάδας
Ζήτησε να λάβει την εικόνα
- για κάθε επαφή και μέλος ομάδας .\nΛάβετε υπόψη: εάν έχετε πολλές συνδέσεις, η κατανάλωση της μπαταρίας και της κυκλοφορίας μπορεί να είναι σημαντικά υψηλότερη και ορισμένες συνδέσεις μπορεί να αποτύχουν.]]>
- Προσθήκη επαφής : για να δημιουργήσετε έναν νέο σύνδεσμο πρόσκλησης ή να συνδεθείτε μέσω ενός συνδέσμου που λάβατε.]]>
- Καλύτερο για τη ζωή της μπαταρίας . Θα λαμβάνετε ειδοποιήσεις μόνο όταν εκτελείται η εφαρμογή (ΧΩΡΙΣ υπηρεσία παρασκηνίου).]]>
+ για κάθε επαφή και μέλος ομάδας .\nΛάβε υπόψη: εάν έχεις πολλές συνδέσεις, η κατανάλωση της μπαταρίας και της χρήσης ίντερνετ μπορεί να είναι σημαντικά υψηλότερη και ορισμένες συνδέσεις μπορεί να αποτύχουν.]]>
+ Προσθήκη επαφής : για να δημιουργήσεις ένα νέο σύνδεσμο πρόσκλησης ή να συνδεθείς μέσω ενός συνδέσμου που έλαβες.]]>
+ Καλύτερο για τη ζωή της μπαταρίας . Θα λαμβάνεις ειδοποιήσεις μόνο όταν εκτελείται η εφαρμογή (ΧΩΡΙΣ υπηρεσία παρασκηνίου).]]>
Beta
Καλύτερες κλήσεις
%1$d σφάλμα/τα αρχείου/ων:\n%2$s
- 1 συζήτηση με ένα μέλος
+ 1 συνομιλία με ένα μέλος
1 αναφορά
1 χρόνος
- Σχετικά με χειρηστές
+ Σχετικά με τους χειριστές
Αποδοχή
Αποδοχή
Αποδοχή ως μέλος
@@ -409,32 +408,2117 @@
Αποδοχή αιτήματος επαφής
αποδέχτηκε %1$s
Αποδεχούμενοι όροι
- αποδέχτηκε τη πρόσκληση
+ αποδέχτηκε την πρόσκληση
σε αποδέχτηκε
Αποδοχή μέλους
Προστέθηκαν διακομιστές πολυμέσων και αρχείων
Προστέθηκε διακομιστής μυνημάτων
Προσθήκη φίλων
- Πρόσθετος τόνος 2
+ Επιπρόσθετο χρώμα έμφασης 2
Προσθήκη λίστας
Προσθήκη μυνήματος
- Διεύθυνση ή σύνδεσμος μιας χρήσης;
+ Διεύθυνση ή σύνδεσμος 1-χρήσης;
Ρυθμίσεις διεύθυνσης
Προσθήκη μέλη ομάδας
- Προσθήκη στην λίστα
+ Προσθήκη στη λίστα
Πρόσθεσε τα μέλη της ομάδας σου στις συνομιλίες.
όλα
- Όλα
+ Όλες
Όλες οι συζητήσεις θα διαγραφτούν απο την λίστα %s, και η λίστα θα διαγραφτεί
Όλα τα καινούργια μυνήματα από αυτά τα μέλη θα είναι κρυμμένα!
Επιτρέψτε τα αρχεία και πολυμέσα μόνο αν η επαφή σου το επιτρέπει.
Επιτρέψτε την αναφορά μυνημάτων στους διαχειριστές.
- Επιτρέψτε τις επαφές σας να σας στέλνουν αρχεία και πολυμέσα.
+ Επέτρεψε στις επαφές σου να σου στέλνουν αρχεία και πολυμέσα.
Όλες η αναφορές θα αρχειοθετηθούν για εσένα.
Όλοι οι διακομιστές
Άλλος λόγος
Η εφαρμογή πάντα να τρέχει στο παρασκήνιο
- Αρχειοθέτηση
+ Αρχειοθέτησε
Αρχειοθέτηση όλων των αναφορών;
αρχειοθετημένη αναφορά
+ 4 νέες γλώσσες διεπαφής
+ Γραμμές εργαλείων εφαρμογής
+ αρχειοθετημένη αναφορά από %s
+ Να αρχειοθετηθούν %d αναφορές;
+ Αρχειοθέτηση αναφοράς
+ Αρχειοθέτηση αναφοράς;
+ Αρχειοθέτηση αναφορών
+ Ερώτηση
+ Καλύτερη απόδοση ομάδων
+ Καλύτερη ιδιωτικότητα και ασφάλεια
+ Βιογραφικό:
+ Το βιογραφικό είναι πολύ μεγάλο
+ Αποκλεισμός μελών για όλους;
+ Θόλωμα
+ Μποτ
+ Εσύ και η επαφή σου, μπορείτε να στέλνετε αρχεία και πολυμέσα.
+ Διεύθυνση επιχείρησης
+ Επαγγελματικές συνομιλίες
+ Επαγγελματική σύνδεση
+ Επιχειρήσεις
+ Χρησιμοποιώντας το SimpleX Chat συμφωνείς να:\n- στέλνεις μόνο νόμιμο περιεχόμενο στις δημόσιες ομάδες.\n- σέβεσαι τους άλλους χρήστες – όχι spam.
+ Δεν μπορείς να αλλάξεις το προφίλ
+ δεν μπορείς να στείλεις μηνύματα
+ Καταλανικά, Ινδονησιακά, Ρουμανικά και Βιετναμέζικα – ευχαριστούμε τους χρήστες μας!
+ με μία μόνο επαφή - προσωπικό διαμοιρασμό ή μέσω οποιασδήποτε εφαρμογής μηνυμάτων.]]>
+ με κρυπτογράφηση από άκρη-σε-άκρη και με μετα-κβαντική ασφάλεια σε άμεσα μηνύματα.]]>
+ Επέτρεψε το στο επόμενο παράθυρο διαλόγου για να λαμβάνεις ειδοποιήσεις άμεσα.]]>
+ Συσκευές Xiaomi: ενεργοποίησε το Αυτόματο Ξεκίνημα στις ρυθμίσεις συστήματος για να λειτουργούν οι ειδοποιήσεις.]]>
+ %s.]]>
+ %s.]]>
+ %s.]]>
+ %s.]]>
+ %s βρίσκεται σε κακή κατάσταση]]>
+ Σάρωση QR κωδικού.]]>
+ %s με τον λόγο: %s]]>
+ σαρώσεις τον QR κωδικό στη βιντεοκλήση, ή η επαφή σου να διαμοιραστεί ένα σύνδεσμο πρόσκλησης.]]>
+ δείξε τον QR κωδικό στη βιντεοκλήση, ή μοιράσου το σύνδεσμο.]]>
+ (νέο)]]>
+ (αυτή η συσκευή v%s)]]>
+ κρυπτογράφηση από άκρη-σε-άκρη.]]>
+ κρυπτογράφηση από άκρη-σε-άκρη με πλήρη εμπιστευτικότητα, δυνατότητα απόρριψης και ανάκτηση μετά από παραβίαση.]]>
+ κβαντο-ανθεκτική κρυπτογράφηση e2e και με πλήρη εμπιστευτικότητα, δυνατότητα απόρριψης και ανάκτηση μετά από παραβίαση.]]>
+ %s έχει μη υποστηριζόμενη έκδοση. Βεβαιώσου ότι χρησιμοποιείς την ίδια έκδοση και στις δύο συσκευές.]]>
+ %s είναι απασχολημένο]]>
+ %s είναι ανενεργό]]>
+ %s δεν υπάρχει]]>
+ %s έχει αποσυνδεθεί]]>
+ %s έχει αποσυνδεθεί]]>
+ Άνοιγμα στην εφαρμογή κινητού, μετά πάτα Σύνδεση μέσα στην εφαρμογή.]]>
+ Χρήση από τον υπολογιστή στην εφαρμογή του κινητού και σκάναρε τον QR κωδικό.]]>
+ Οδηγό Χρήσης.]]>
+ αποθετήριό μας στο GitHub.]]>
+ Χρήση .onion hosts σε Όχι, αν ο διακομιστής μεσολάβησης SOCKS δεν τα υποστηρίζει.]]>
+ %s.]]>
+ %s.]]>
+ %s.]]>
+ %s.]]>
+ %1$s!]]>
+ %s]]>
+ Χρήση μπαταρίας εφαρμογής / Απεριόριστη στις ρυθμίσεις της εφαρμογής.]]>
+ το SimpleX τρέχει στο παρασκήνιο αντί να χρησιμοποιεί ειδοποιήσεις push.]]>
+ Χρήση μπαταρίας εφαρμογής / Απεριόριστη στις ρυθμίσεις της εφαρμογής.]]>
+ %s, αποδέξου τους όρους χρήσης.]]>
+ %1$s.]]>
+ %1$s.]]>
+ %1$s.]]>
+ %1$s.]]>
+ πρέπει να χρησιμοποιείς την ίδια βάση δεδομένων σε δύο συσκευές.]]>
+ Άνοιγμα στην εφαρμογή κινητού κουμπί.]]>
+ συνδεθείς με τους δημιουργούς του SimpleX Chat για να κάνεις ερωτήσεις και να λαμβάνεις ενημερώσεις.]]>
+ μόνο αφού γίνει αποδεκτό το αίτημά σου.]]>
+ Να αλλάξεις την αυτόματη διαγραφή μηνυμάτων;
+ Αλλαγή των προφίλ συνομιλίας
+ Αλλαγή λίστας
+ Αλλαγή σειράς
+ Συνομιλία
+ Η συνομιλία υπάρχει ήδη!
+ Συνομιλίες με μέλη
+ Η συνομιλία θα διαγραφεί για όλα τα μέλη – αυτή η ενέργεια δεν μπορεί να αναιρεθεί!
+ Η συνομιλία θα διαγραφεί για εσένα – αυτή η ενέργεια δεν μπορεί να αναιρεθεί!
+ Συνομίλησε με τους διαχειριστές
+ Συνομίλησε με τους διαχειριστές
+ Συνομίλησε με τους διαχειριστές
+ Συνομίλησε με μέλος
+ Συνομιλία με μέλη, πριν συνενωθούν.
+ Έλεγχος μηνυμάτων κάθε 10 λεπτά
+ Τα κομμάτια κατέβηκαν
+ Τα τμήματα των αρχείων ανέβηκαν
+ Καθάρισε
+ Καθαρισμός
+ Καθαρισμός
+ Καθαρισμός συνομιλίας
+ Καθαρισμός συνομιλίας;
+ Καθαρισμός ιδιωτικών σημειώσεων;
+ Καθαρισμός επαλήθευσης
+ Κάνε κλικ στο κουμπί πληροφοριών δίπλα στο πεδίο διεύθυνσης για να επιτρέψεις τη χρήση του μικροφώνου.
+ Κουμπί κλεισίματος
+ χρωματισμένο
+ Λειτουργία χρώματος
+ Έρχεται σύντομα!
+ Παράβαση των κατευθυντήριων γραμμών της κοινότητας
+ Σύγκριση αρχείου
+ Σύγκρινε τους κωδικούς ασφαλείας με τις επαφές σου.
+ ολοκληρώθηκε
+ Ολοκληρωμένο
+ Οι όροι έγιναν αποδεκτοί στις: %s.
+ Όροι χρήσης
+ Οι όροι θα γίνουν αποδεκτοί για τους ενεργούς χειριστές μετά από 30 ημέρες.
+ Οι όροι θα γίνουν αποδεκτοί στις: %s.
+ Οι όροι θα γίνουν αυτόματα αποδεκτοί για τους ενεργούς χειριστές στις: %s.
+ Διαμορφωμένοι SMP διακομιστές
+ Διαμορφωμένοι XFTP διακομιστές
+ Διαμορφωμένοι ICE διακομιστές
+ Διαμόρφωση χειριστών διακομιστή
+ Επιβεβαίωσε
+ Επιβεβαίωση διαγραφής επαφής;
+ Επιβεβαίωση αναβαθμίσεων βάσης δεδομένων
+ Επιβεβαίωση αρχείων από άγνωστους διακομιστές.
+ Επιβεβαίωση ρυθμίσεων δικτύου
+ Επιβεβαίωση νέας φράσης πρόσβασης…
+ Επιβεβαίωση κωδικού πρόσβασης
+ Επιβεβαίωση κωδικού
+ Επιβεβαίωσε ότι θυμάσαι τη φράση πρόσβασης της βάσης δεδομένων για να τη μεταφέρεις.
+ Επιβεβαίωση μεταφόρτωσης
+ Επιβεβαίωση των διαπιστευτηρίων σου
+ σύνδεση
+ Σύνδεση
+ Σύνδεση
+ Σύνδεση
+ Αυτόματη σύνδεση
+ Απευθείας σύνδεση;
+ συνδεδεμένος
+ συνδεδεμένος
+ συνδεδεμένος
+ Συνδεδεμένος
+ Συνδεδεμένος
+ Συνδεδεμένος υπολογιστής
+ Συνδεδεμένοι διακομιστές
+ Συνδέσου γρηγορότερα! 🚀
+ Συνδέεται
+ κλήση σε σύνδεση…
+ Σύνδεση κλήσης
+ σύνδεση (σε εξέλιξη)
+ συνδέεται (μέσω πρόσκλησης)
+ Σύνδεση με την επαφή, περίμενε ή δοκίμασε αργότερα!
+ Κατάσταση σύνδεσης και διακομιστών.
+ Σύνδεση μπλοκαρισμένη
+ Η σύνδεση έχει μπλοκαριστεί από τον χειριστή του διακομιστή:\n%1$s.
+ Η σύνδεση δεν είναι έτοιμη.
+ Η σύνδεση απαιτεί επαναδιαπραγμάτευση κρυπτογράφησης.
+ Συνδέσεις
+ Ασφάλεια σύνδεσης
+ Η σύνδεση διακόπηκε
+ Η σύνδεση διακόπηκε
+ Η σύνδεση με τον υπολογιστή βρίσκεται σε κακή κατάσταση
+ - σύνδεση με υπηρεσία καταλόγου (δοκιμαστικό)!\n- επιβεβαιώσεις παράδοσης (εώς 20 μέλη).\n- γρηγορότερα και πιο σταθερά.
+ Συνδέσου με τους φίλους σου πιο γρήγορα.
+ η επαφή %1$s άλλαξε σε %2$s
+ Η επαφή ελέγχθηκε
+ η επαφή διαγράφηκε
+ Η επαφή διαγράφηκε!
+ η επαφή απενεργοποιήθηκε
+ Κρυμμένη επαφή:
+ Η επαφή διαγράφηκε.
+ η επαφή δεν είναι έτοιμη
+ ΑΙΤΗΣΕΙΣ ΕΠΑΦΩΝ ΑΠΟ ΟΜΑΔΕΣ
+ Επαφές
+ η επαφή πρέπει να αποδεχτεί…
+ Η επαφή θα διαγραφεί – αυτή η ενέργεια δεν μπορεί να αναιρεθεί!
+ Το περιεχόμενο παραβιάζει τους όρους χρήσης
+ Εικονίδιο περιεχομένου
+ Συνέχεια
+ Συνέχεια
+ Συνεισφορά
+ Έλεγξε το δίκτυό σου
+ Η συζήτηση διαγράφηκε!
+ Αντιγράφηκε στο πρόχειρο
+ Σφάλμα αντιγραφής
+ Έκδοση πυρήνα: v%s
+ Γωνία
+ Δημιουργία
+ Δημιουργία συνδέσμου 1-χρήσης
+ Δημιούργησε μια διεύθυνση για να μπορούν οι άλλοι να συνδεθούν μαζί σου.
+ Δημιουργήθηκε
+ Δημιουργήθηκε στις
+ Δημιουργήθηκε στις: %s
+ Δημιουργία λίστας
+ Δημιουργία νέου προφίλ στην εφαρμογή υπολογιστή. 💻
+ Δημιουργία συνδέσμου 1-χρήσης
+ Δημιουργία ουράς
+ Δημιούργησε τη διεύθυνσή σου
+ Δημιουργία συνδέσμου αρχειοθέτησης
+ Δημιουργία συνδέσμου…
+ Κρίσιμο σφάλμα
+ (τρέχον)
+ Το κείμενο των τρεχουσών προϋποθέσεων δεν φορτώθηκε, μπορείς να τις δεις μέσω αυτού του συνδέσμου:
+ Το μέγιστο υποστηριζόμενο μέγεθος αρχείου αυτήν τη στιγμή είναι %1$s.
+ Τρέχων κωδικός πρόσβασης
+ Τρέχουσα φράση πρόσβασης…
+ Τρέχον προφίλ
+ προσαρμοσμένος
+ Προσαρμόσιμη μορφή μηνύματος.
+ Προσάρμοσε και μοίρασε τα θέματα χρωμάτων.
+ Προσαρμογή θέματος
+ Προσαρμοσμένα θέματα
+ Προσαρμοσμένος χρόνος
+ Σκούρο
+ Σκούρο
+ Σκοτεινή λειτουργία
+ Χρώματα σκοτεινής λειτουργίας
+ Σκούρο θέμα
+ Υποβάθμιση βάσης δεδομένων
+ Η βάση δεδομένων κρυπτογραφήθηκε!
+ Η φράση πρόσβασης για την κρυπτογράφηση της βάσης δεδομένων θα ενημερωθεί.
+ Η φράση πρόσβασης για την κρυπτογράφηση της βάσης δεδομένων θα ενημερωθεί και θα αποθηκευτεί στις ρυθμίσεις.
+ Η φράση κρυπτογράφησης της βάσης δεδομένων θα ενημερωθεί και θα αποθηκευτεί στο Keystore.
+ Σφάλμα βάσης δεδομένων
+ Αναγνωριστικό βάσης δεδομένων
+ Αναγνωριστικό βάσης δεδομένων: %d
+ Αναγνωριστικά βάσης δεδομένων και επιλογή απομόνωσης μεταφοράς.
+ Η βάση δεδομένων είναι κρυπτογραφημένη με τυχαία φράση πρόσβασης. Παρακαλώ άλλαξέ την πριν την εξαγωγή.
+ Η βάση δεδομένων είναι κρυπτογραφημένη με τυχαία φράση πρόσβασης, μπορείς να την αλλάξεις.
+ Η μετεγκατάσταση της βάσης δεδομένων βρίσκεται σε εξέλιξη.\nΜπορεί να χρειαστούν λίγα λεπτά.
+ Φράση πρόσβασης βάσης δεδομένων
+ Φράση πρόσβασης βάσης δεδομένων και εξαγωγή αυτής
+ Η φράση πρόσβασης της βάσης δεδομένων διαφέρει από αυτή που έχει αποθηκευτεί στο Keystore.
+ Η φράση πρόσβασης της βάσης δεδομένων απαιτείται για να ανοίξεις τη συνομιλία.
+ Αναβάθμιση βάσης δεδομένων
+ Η έκδοση της βάσης δεδομένων είναι νεότερη από την εφαρμογή, αλλά δεν υπάρχει δυνατότητα υποβάθμισης για: %s
+ Η βάση δεδομένων θα κρυπτογραφηθεί.
+ Η βάση δεδομένων θα κρυπτογραφηθεί και η φράση πρόσβασης θα αποθηκευτεί στις ρυθμίσεις.
+ Η βάση δεδομένων θα κρυπτογραφηθεί και η φράση πρόσβασης θα αποθηκευτεί στο Keystore.
+ ημέρες
+ %d συνομιλία/ες
+ %d συνομιλίες με μέλη
+ %d επαφή/ές επιλέχθηκε/καν
+ %dη
+ %d ημέρα
+ %d ημέρες
+ Αποστολή για αποσφαλμάτωση
+ Αποκεντρωμένο
+ Σφάλμα αποκωδικοποίησης
+ Σφάλμα αποκρυπτογράφησης
+ σφάλματα αποκρυπτογράφησης
+ προεπιλογή (%s)
+ προεπιλογή (%s)
+ Διέγραψε
+ Διαγραφή
+ Διαγραφή
+ Διαγραφή
+ Διαγραφή διεύθυνσης
+ Διαγραφή διεύθυνσης;
+ Διαγραφή μετά
+ Διαγραφή όλων των αρχείων
+ Διαγραφή και ειδοποίηση επαφής
+ Διαγραφή συνομιλίας
+ Διαγραφή συνομιλίας
+ Διαγραφή συνομιλίας;
+ Διαγραφή μηνυμάτων συνομιλίας από τη συσκευή σου.
+ Διαγραφή προφίλ συνομιλίας
+ Διαγραφή προφίλ συνομιλίας;
+ Διαγραφή προφίλ συνομιλίας;
+ Διαγραφή προφίλ συνομιλίας για
+ Διαγραφή συνομιλίας με μέλος;
+ Διαγραφή επαφής
+ Διαγραφή επαφής;
+ Διαγράφηκε
+ Διαγράφηκε στις
+ Διαγραφή βάσης δεδομένων
+ Διαγραφή βάσης δεδομένων από αυτήν τη συσκευή
+ Διαγράφηκε στις: %s
+ διεγραμμένη επαφή
+ διεγραμμένη ομάδα
+ Διαγραφή %d μηνυμάτων;
+ Διαγραφή %d μηνυμάτων μελών;
+ Διαγραφή αρχείου
+ Διαγραφή μηνυμάτων και πολυμέσων;
+ Διαγραφή μηνυμάτων για όλα τα προφίλ συνομιλίας
+ Διαγραφή για όλους
+ Διαγραφή για μένα
+ Διαγραφή ομάδας
+ Διαγραφή ομάδας;
+ Διαγραφή εικόνας
+ Διαγραφή συνδέσμου
+ Διαγραφή συνδέσμου;
+ Διαγραφή λίστας;
+ Διαγραφή μηνύματος μέλους;
+ Διαγραφή μηνυμάτων μέλους
+ Διαγραφή μηνυμάτων μέλους;
+ Διαγραφή μηνύματος;
+ Διαγραφή μηνυμάτων
+ Διαγραφή μηνυμάτων
+ Διαγραφή μηνυμάτων μετά
+ Διαγραφή ή διαχείριση εώς 200 μηνυμάτων.
+ Να διαγραφεί η εκκρεμής σύνδεση;
+ Διαγραφή προφίλ
+ Διαγραφή ουράς
+ Διαγραφή αναφοράς
+ Διαγραφή διακομιστή
+ Διαγραφή εώς 20 μηνυμάτων ταυτόχρονα.
+ Διαγραφή χωρίς ειδοποίηση
+ Σφάλματα διαγραφής
+ Παράδοση
+ Επιβεβαιώσεις παράδοσης!
+ Οι επιβεβαιώσεις παράδοσης είναι απενεργοποιημένες!
+ Απαρχαιωμένες επιλογές
+ Περιγραφή
+ Περιγραφή πολύ μεγάλη
+ Υπολογιστής
+ Διεύθυνση υπολογιστή
+ Η έκδοση της εφαρμογής για υπολογιστή %s δεν είναι συμβατή με αυτήν την εφαρμογή.
+ Συσκευές υπολογιστή
+ Ο υπολογιστής έχει μη υποστηριζόμενη έκδοση. Βεβαιώσου ότι χρησιμοποιείς την ίδια έκδοση και στις δύο συσκευές.
+ Ο υπολογιστής έχει λάθος κωδικό πρόσκλησης
+ Ο υπολογιστής είναι απασχολημένος
+ Ο υπολογιστής είναι ανενεργός
+ Ο υπολογιστής έχει αποσυνδεθεί
+ Η διεύθυνση διακομιστή προορισμού %1$s δεν είναι συμβατή με τις ρυθμίσεις του διακομιστή προώθησης %2$s.
+ Σφάλμα διακομιστή προορισμού: %1$s
+ Η έκδοση του διακομιστή προορισμού %1$s δεν είναι συμβατή με τον διακομιστή προώθησης %2$s.
+ Αναλυτικά στατιστικά
+ Λεπτομέρειες
+ Επιλογές προγραμματιστή
+ Εργαλεία προγραμματιστή
+ ΣΥΣΚΕΥΗ
+ Η επαλήθευση συσκευής είναι απενεργοποιημένη. Απενεργοποιείται το SimpleX Lock.
+ Η επαλήθευση συσκευής δεν είναι ενεργοποιημένη. Μπορείς να ενεργοποιήσεις το SimpleX Lock από τις Ρυθμίσεις, αφού πρώτα ενεργοποιήσεις την επαλήθευση συσκευής.
+ Συσκευές
+ %d αρχείο/α με συνολικό μέγεθος %s
+ %d συμβάντα ομάδας
+ %dω
+ %dώρα
+ %dώρες
+ διαφορετική μετεγκατάσταση στην εφαρμογή/βάση δεδομένων: %s / %s
+ Διαφορετικά ονόματα, avatar και απομόνωση μεταφοράς.
+ άμεσα
+ Άμεσα μηνύματα
+ Τα απευθείας μηνύματα μεταξύ των μελών, είναι απαγορευμένα.
+ Τα απευθείας μηνύματα μεταξύ των μελών, είναι απαγορευμένα σε αυτή τη συνομιλία
+ Τα απευθείας μηνύματα μεταξύ των μελών, είναι απαγορευμένα σε αυτήν την ομάδα.
+ Απενεργοποίηση
+ Απενεργοποίηση
+ Απενεργοποίηση αυτόματης διαγραφής μηνυμάτων;
+ απενεργοποιημένο
+ απενεργοποιημένο
+ Απενεργοποιημένο
+ Απενεργοποίηση διαγραφής μηνυμάτων
+ Απενεργοποίηση για όλους
+ Απενεργοποίηση για όλες τις ομάδες
+ πενεργοποίηση (διατήρηση παρακάμψεων ομάδας)
+ Απενεργοποίηση (διατήρηση παρακάμψεων)
+ Απενεργοποίηση ειδοποιήσεων
+ Απενεργοποίηση αναφορών παράδοσης;
+ Απενεργοποιίηση αναφορών παράδοσης για τις ομάδες;
+ Απερνεργοποίση SimpleX Lock
+ Μήνυμα που εξαφανίζεται
+ Μηνύματα που εξαφανίζονται
+ Μηνύματα που εξαφανίζονται
+ Είναι απαγορευμένα τα μηνύματα που εξαφανίζονται.
+ Είναι απαγορευμένα τα μηνύματα που εξαφανίζονται σε αυτήν τη συνομιλία.
+ Να εξαφανιστεί σε
+ Να εξαφανιστεί σε: %s
+ Αποσύνδεση
+ Αποσύνδεση
+ Αποσύνδεση υπολογιστή;
+ Αποσυνδεδεμένος
+ Αποσυνδεδεμένος με το λόγο: %s
+ Αποσύνδεση τηλεφώνων
+ Ανιχνεύσιμο μέσω τοπικού δικτύου
+ Ανακάλυψε και συνδέσου σε ομάδες
+ Ανακάλυψη μέσω τοπικού δικτύου
+ Το εμφανιζόμενο όνομα δεν μπορεί να περιέχει κενά.
+ %dμ
+ %dμηνύματα
+ %dμηνύματα μπλοκαρισμενα από το διαχειριστή
+ %d λπτ
+ %d λεπτά
+ %dμήνας
+ %d μήνες
+ %dμν
+ Να μη σταλεί το ιστορικό σε νέα μέλη.
+ ΜΗΝ στέλνεις μηνύματα απευθείας, ακόμα κι αν ο δικός σου διακομιστής ή ο διακομιστής προορισμού δεν υποστηρίζει ιδιωτική δρομολόγηση.
+ Μην χρησιμοποιείς διαπιστευτήρια με το διακομιστή μεσολάβησης (proxy).
+ ΜΗΝ χρησιμοποιείς ιδιωτική δρομολόγηση.
+ Μην δημιουργήσεις διεύθυνση
+ Μην ενεργοποιήσεις
+ Μην χάσεις σημαντικά μηνύματα.
+ Να μην εμφανιστεί ξανά
+ Υποβάθμιση και άνοιγμα συνομιλίας
+ Κατέβασμα
+ Κατέβασμα
+ Κατέβηκε
+ Κατεβασμένα αρχεία
+ Σφάλματα λήψης
+ Η λήψη απέτυχε
+ Λήψη αρχείου
+ Η αναβάθμιση εφαρμογής βρίσκεται σε εξέλιξη, μην κλείσεις την εφαρμογή
+ Λήψη αρχείου αρχειοθέτησης
+ Λήψη λεπτομερειών συνδέσμου
+ Κατέβασε νέες εκδόσεις από το GitHub.
+ Κατέβασμα %s (%s)
+ %d αναφορές
+ %dδ
+ %dδευτ
+ %dδευτερόλεπτα
+ Διπλότυπο εμφανιζόμενο όνομα!
+ διπλότυπο μήνυμα
+ διπλότυπα
+ %dε
+ %d εβδομάδα
+ %d εβδομάδες
+ e2e κρυπτογραφημένο
+ e2e κρυπτογραφημένη φωνητική κλήση
+ e2e κρυπτογραφημένη βιντεοκλήση
+ Ακουστικό
+ Επεξεργάσου
+ Επεξεργασία
+ επεξεργάστηκε
+ Επεξεργασία προφίλ ομάδας
+ Επεξεργασία εικόνας
+ Email
+ Ενεργοποίηση
+ Ενεργοποίηση αυτόματης διαγραφής μηνυμάτων
+ Ενεργοποίηση φωνητικών κλήσεων από την οθόνη κλειδώματος μέσω των Ρυθμίσεων
+ Ενεργοποίηση πρόσβασης κάμερας
+ ενεργοποιημένο
+ Ενεργοποιημένο για
+ ενεργοποιημένο για την επαφή
+ ενεργοποιημένο για εσένα
+ Ενεργοποίηση μηνυμάτων που εξαφανίζονται από προεπιλογή.
+ Ενεργοποίησε το Flux στις ρυθμίσεις Δικτύου & διακομιστών για καλύτερη προστασία μεταδεδομένων.
+ Ενεργοποίηση για όλα
+ Ενεργοποίηση για όλες τις ομάδες
+ Ενεργοποίηση στις άμεσες συνομιλίες (ΔΟΚΙΜΑΣΤΙΚΟ)!
+ Ενεργοποίηση (διατήρηση παρακάμψεων ομάδας)
+ Ενεργοποίηση (διατήρηση παρακάμψεων)
+ Ενεργοποίηση κλειδώματος
+ Ενεργοποίηση αρχείων καταγραφής δραστηριότητας
+ Ενεργοποίηση αναφορών παράδοσης;
+ Ενεργοποίηση αναφορών παράδοσης για τις ομάδες;
+ Ενεργοποίηση αυτοκαταστροφής
+ Ενεργοποίηση κωδικού αυτοκαταστροφής
+ Ενεργοποίηση SImpleX Lock
+ Ενεργοποίηση TCP keep-alive
+ Κρυπτογράφηση
+ Κρυπτογράφηση βάσης δεδομένων;
+ Κρυπτογραφημένη βάση δεδομένων
+ κρυπτογράφηση συμφωνήθηκε
+ κρυπτογράφηση συμφωνήθηκε για %s
+ κρυπτογράφηση οκ
+ κρυπτογράφηση οκ για %s
+ επιτρέπεται επαναδιαπραγμάτευση κρυπτογράφησης
+ επιτρέπεται επαναδιαπραγμάτευση κρυπτογράφησης για %s
+ Σφάλμα κατά την επαναδιαπραγμάτευση κρυπτογράφησης
+ Αποτυχία κατά την επαναδιαπραγμάτευση κρυπτογράφησης
+ Επαναδιαπραγμάτευση κρυπτογράφησης σε εξέλιξη.
+ απαιτείται επαναδιαπραγμάτευση κρυπτογράφησης
+ απαιτείται επαναδιαπραγμάτευση κρυπτογράφησης για %s
+ Κρυπτογράφηση τοπικών αρχείων
+ Κρυπτογράφηση αποθηκευμένων αρχείων & πολυμέσων
+ Τερματισμός κλήσης
+ τερματίστηκε
+ Εισήγαγε σωστή φράση πρόσβασης.
+ Εισήγαγε όνομα ομάδας:
+ Εισήγαγε κωδικό πρόσβασης
+ Εισήγαγε φράση πρόσβασης
+ Εισήγαγε φράση πρόσβασης…
+ Εισήγαγε κωδικό στην αναζήτηση
+ Χειροκίνητη εισαγωγή διακομιστή
+ Εισήγαγε το όνομα συσκευής…
+ Εισήγαγε το μήνυμα καλωσορίσματος…
+ Εισήγαγε το μήνυμα καλωσορίσματος… (προαιρετικό)
+ Εισήγαγε το όνομά σου:
+ Σφάλμα
+ Σφάλμα
+ Σφάλμα
+ Σφάλμα
+ Σφάλμα: %1$s
+ Σφάλμα κατά την ακύρωση αλλαγής διεύθυνσης
+ Σφάλμα κατά την αποδοχή των όρων
+ Σφάλμα κατά την αποδοχή του αιτήματος επαφής
+ Σφάλμα κατά την αποδοχή μέλους
+ Επιδιόρθωση σύνδεσης;
+ Επιδιόρθωση σύνδεσης;
+ Διόρθωσε την κρυπτογράφηση μετά από επαναφορά αντιγράφων ασφαλείας.
+ Η επιδιόρθωση δεν υποστηρίζεται από την επαφή
+ Η επιδιόρθωση δεν υποστηρίζεται από μέλος ομάδας
+ Αντιστροφή κάμερας
+ Μέγεθος γραμματοσειράς
+ Για όλους τους διαχειριστές
+ για καλύτερη ιδιωτικότητα μεταδεδομένων
+ Για το προφίλ συνομιλίας %s:
+ ΓΙΑ ΚΟΝΣΟΛΑ
+ Για όλους
+ Για παράδειγμα, αν η επαφή σου λαμβάνει μηνύματα μέσω κάποιου SimpleX Chat διακομιιστή, η εφαρμογή σου θα τα παραδίδει μέσω ενός Flux διακομιστή.
+ Για μένα
+ Για ιδιωτική δρομολόγηση
+ Για μέσα κοινωνικής δικτύωσης
+ Προώθηση
+ Προώθηση %1$s μηνύματος/ων;
+ Προώθηση και αποθήκευση μηνυμάτων
+ προωθήθηκε
+ Προωθήθηκε
+ Προωθήθηκε από
+ Προώθηση %1$s μηνυμάτων
+ Διακομιστής προώθησης: %1$s\nΣφάλμα διακομιστή προορισμού: %2$s
+ Διακομιστής προώθησης: %1$s\nΣφάλμα: %2$s
+ Ο διακομιστής προώθησης %1$s δεν κατάφερε να συνδεθεί με τον διακομιστή προορισμού %2$s. Δοκίμασε ξανά αργότερα.
+ Η διεύθυνση του διακομιστή προώθησης είναι ασύμβατη με τις ρυθμίσεις του δικτύου: %1$s.
+ Η έκδοση του διακομιστή προώθησης είναι ασύμβατη με τις ρυθμίσεις του δικτύου: %1$s.
+ Προώθηση μηνύματος…
+ Προώθηση μηνυμάτων…
+ Προώθηση μηνυμάτων χωρίς τα αρχεία;
+ Προώθησε μέχρι και 20 μηνύματα μαζί.
+ Βρέθηκε υπολογιστής
+ Διεπαφή στα γαλλικά
+ Από τη Συλλογή
+ Πλήρης σύνδεσμος
+ Πλήρης σύνδεσμος
+ Πλήρες όνομα:
+ Πλήρως αποκεντρωμένο – ορατό μόνο στα μέλη.
+ Περαιτέρω μειωμένη κατανάλωση μπαταρίας
+ Λάβε ειδοποίηση όταν σε αναφέρουν.
+ Καλησπέρα!
+ Καλημέρα!
+ Χορήγησε στις ρυθμίσεις
+ Χορήγησε άδειες
+ Χορήγησε άδεια/ες για να κάνεις φωνητικές κλήσεις
+ Ομάδα
+ Ομάδα
+ Η ομάδα υπάρχει ήδη!
+ η ομάδα διαγράφηκε
+ Πλήρες όνομα ομάδας:
+ Ανενεργή ομάδα
+ Έληξε η πρόσκληση ομάδας
+ Η πρόσκληση για την ομάδα δεν ισχύει πια, αφαιρέθηκε από τον αποστολέα.
+ η ομάδα διαγράφηκε
+ Σύνδεσμος ομάδας
+ Σύνδεσμοι ομάδας
+ Διαχείριση ομάδας
+ Η ομάδα δεν βρέθηκε!
+ Προτιμήσεις ομάδας
+ Το προφίλ της ομάδας αποθηκεύεται στις συσκευές των μελών και όχι στους διακομιστές.
+ το προφίλ της ομάδας ανανεώθηκε
+ Ομάδες
+ Μήνυμα καλωσορίσματος ομάδας
+ Η ομάδα θα διαγραφεί για όλα τα μέλη – αυτή η ενέργεια δεν μπορεί να αναιρεθεί!
+ Η ομάδα θα διαγραφεί για σένα – αυτή η ενέργεια δεν μπορεί να αναιρεθεί!
+ Τερματισμός κλήσης
+ Ακουστικά
+ βοήθεια
+ ΒΟΗΘΕΙΑ
+ Βοήθησε τους διαχειριστές να διαχειρίζονται τις ομάδες τους.
+ Γεια σου!\nΣυνδέσου μαζί μου μέσω SimpleX Chat: %s
+ Κρυφό
+ Κρυμμένα προφίλ συνομιλίας
+ Κρυφό συνθηματικό προφίλ
+ Κρύψε
+ Κρύψε
+ Κρύψε
+ Κρύψε:
+ Απόκρυψη της οθόνης της εφαρμογής στις πρόσφατες εφαρμογές.
+ Απόκρυψη επαφής και μηνύματος
+ Απόκρυψη προφίλ
+ Ιστορικό
+ Το ιστορικό δεν αποστέλλεται σε νέα μέλη.
+ Φιλοξένησε
+ ώρες
+ Πως επηρεάζει τη μπαταρία
+ Πως βοηθάει την ιδιωτικότητα
+ Πως δουλεύει
+ Πως δουλεύει το SimpleX
+ Πως να
+ Πως να το χρησιμοποιήσεις
+ Πως να χρησιμοποιήσεις markdown σύνταξη
+ Πως να χρησιμοποιήσεις τους διακομιστές σου
+ Διεπαφή στα Ουγγρικά και Τουρκικά
+ ICE διακομιστές (ένας σε κάθε γραμμή)
+ Αν δεν μπορείς να συναντηθείς προσωπικά, δείξε τον QR κωδικό σε μια βιντεοκλήση ή μοιράσου τον σύνδεσμο.
+ Αν επιλέξεις να απορρίψεις, ο αποστολέας ΔΕΝ θα ειδοποιηθεί.
+ Αν επιβεβαιώσεις, οι διακομιστές μηνυμάτων θα μπορούν να δουν τη διεύθυνση IP σου, και ο πάροχός σου – σε ποιους διακομιστές συνδέεσαι.
+ Αν εισάγεις αυτόν τον κωδικό κατά το άνοιγμα της εφαρμογής, όλα τα δεδομένα της εφαρμογής θα διαγραφούν οριστικά!
+ Αν εισάγεις τον κωδικό αυτοκαταστροφής κατά το άνοιγμα της εφαρμογής:
+ Αν έλαβες σύνδεσμο πρόσκλησης για SimpleX Chat, μπορείς να τον ανοίξεις στον περιηγητή σου:
+ Αγνόησε
+ Εικόνα
+ Εικόνα
+ Η εικόνα αποθηκεύτηκε στη Συλλογή
+ Η εικόνα στάλθηκε
+ Η εικόνα θα ληφθεί όταν η επαφή σου ολοκληρώσει τη μεταφόρτωσή της.
+ Η εικόνα θα ληφθεί όταν η επαφή σου είναι συνδεδεμένη, περίμενε ή έλεγξε αργότερα!
+ Άμεσα
+ Ανοσοποιημένο στο spam
+ Εισαγωγή
+ Εισαγωγή βάσης δεδομένων συνομιλίας;
+ Εισαγωγή βάσης δεδομένων
+ Η εισαγωγή απέτυχε
+ Εισαγωγή αρχείου αρχειοθέτησης
+ Εισαγωγή θέματος
+ Σφάλμα εισαγωγής θέματος
+ Βελτιωμένη πλοήγηση συνομιλίας
+ Βελτιωμένη παράδοση μηνύματος
+ Βελτιωμένη παράδοση μηνύματος
+ Βελτιωμένη ιδιωτικότητα και ασφάλεια
+ Βελτιωμένη διαμόρφωση διακομιστή
+ ανενεργό
+ Ακατάλληλο περιεχόμενο
+ Ακατάλληλο προφίλ
+ Ήχοι κατά τη διάρκεια κλήσης
+ Ανώνυμο
+ Ανώνυμες ομάδες
+ Ανώνυμη λειτουργία
+ Η ανώνυμη λειτουργία προστατεύει το απόρρητό σου χρησιμοποιώντας ένα νέο τυχαίο προφίλ για κάθε επαφή.
+ ανώνυμα μέσω συνδέσμου διεύθυνσης επαφής
+ ανώνυμα μέσω συνδέσμου ομάδας
+ ανώνυμα μέσω συνδέσμου 1-χρήσης
+ Εισερχόμενη φωνητική κλήση
+ Εισερχόμενη βιντεοκλήση
+ Ασύμβατη έκδοση βάσης δεδομένων
+ Ασύμβατη έκδοση
+ Λανθασμένος κωδικός πρόσβασης
+ Λανθασμένος κωδικός ασφάλειας!
+ Μεγένθυση γραμματοσειράς
+ έμμεσο (%1$s)
+ Πληροφορίες
+ Αρχικός ρόλος
+ Για να συνεχίσεις, η συνομιλία θα πρέπει να σταματήσει.
+ Σε απάντηση του
+ Εγκαταστάθηκε επιτυχημένα
+ Εγκατέστησε το SimpleX Chat για το τερματικό
+ Εγκατάσταση αναβάθμισης
+ Άμεσα
+ Άμεσες ειδοποιήσεις
+ Άμεσες ειδοποιήσεις!
+ Οι άμεσες ειδοποιήσεις είναι απενεργοποιημένες!
+ ΧΡΩΜΑΤΑ ΔΙΕΠΑΦΗΣ
+ Εσωτερικό σφάλμα
+ μη έγκυρη συνομιλία
+ Μη έγκυρος σύνδεσμος
+ μη έγκυρα δεδομένα
+ Μη έγκυρο εμφανιζόμενο όνομα!
+ Μη έγκυρος σύνδεσμος
+ Μη έγκυρος σύνδεσμος
+ Μη έγκυρος σύνδεσμος!
+ μη έγκυρη διαμόρφωση μηνύματος
+ Μη έγκυρη επιβεβαίωση μετεγκατάστασης
+ Μη έγκυρο όνομα!
+ Μη έγκυρος QR κωδικός
+ Μη έγκυρος QR κωδικός
+ Μη έγκυρη διεύθυνση διακομιστή!
+ Η πρόσκληση έληξε!
+ πρόσκληση στην ομάδα %1$s
+ Προσκάλεσε
+ Προσκάλεσε
+ προσκαλεσμένος
+ προσκεκλημένος %1$s
+ προσκεκλημένος για σύνδεση
+ προσκεκλημένος μέσω του συνδέσμου της ομάδας σου
+ Προσκάλεσε φίλους
+ Προσκάλεσε μέλη
+ Προσκάλεσε μέλη
+ Προσκάλεσε για συνομιλία
+ Προσκάλεσε σε ομάδα
+ Οριστική διαγραφή μηνύματος
+ Η οριστική διαγραφή μηνύματος απαγορεύεται.
+ Η οριστική διαγραφή μηνύματος απαγορεύεται σε αυτή τη συνομιλία.
+ Διεπαφή στα Ιταλικά
+ πλάγια γραφή
+ Σου επιτρέπει να έχεις πολλές ανώνυμες συνδέσεις χωρίς κοινά δεδομένα μεταξύ τους σε ένα μόνο προφίλ συνομιλίας.
+ Μπορεί να συμβεί όταν:\n1. Τα μηνύματα λήγουν στον αποστολέα μετά από 2 ημέρες ή στον διακομιστή μετά από 30 ημέρες.\n2. Η αποκρυπτογράφηση μηνύματος απέτυχε, επειδή εσύ ή η επαφή σου χρησιμοποιήσατε παλιό αντίγραφο ασφαλείας της βάσης δεδομένων.\n3. Η σύνδεση έχει παραβιαστεί.
+ Μπορεί να συμβεί όταν εσύ ή η σύνδεσή σου χρησιμοποιήσατε παλιό αντίγραφο ασφαλείας της βάσης δεδομένων.
+ Προστατεύει τη διεύθυνση IP και τις συνδέσεις σου.
+ Διεπαφή στα Ιαπωνικά και Πορτογαλικά
+ Συμμετοχή
+ Συμμετοχή ως %s
+ Συμμετοχή στην ομάδα
+ Συμμετοχή στην ομάδα;
+ Συμμετοχή στις συνομιλίες της ομάδας
+ Ανώνυμη συμμετοχή
+ Συμμετοχή στη ομάδα
+ Θέλεις να συμμετάσχεις στην ομάδα σου;
+ χ
+ Διατήρηση
+ Διατήρηση συνομιλίας
+ Διατήρηση αχρησιμοποίητων προσκλήσεων;
+ Διατήρησε καθαρές τις συνομιλίες σου
+ Διατήρησε τις συνδέσεις
+ Σφάλμα κλειδιού
+ Μεγάλο αρχείο!
+ Μάθε περισσότερα
+ Αποχώρησε
+ Αποχώρησε από τη συνομιλία
+ Αποχώρηση από τη συνομιλία;
+ Αποχώρησε από την ομάδα
+ Αποχώρηση από την ομάδα;
+ αποχώρησε
+ αποχώρησε
+ Λιγότερη κίνηση στα δίκτυα κινητής τηλεφωνίας.
+ Ας μιλήσουμε στο SimpleX Chat
+ Ανοιχτόχρωμο
+ Ανοιχτόχρωμο
+ Ανοιχτόχρωμη λειτουργία
+ Σύνδεσε ένα τηλέφωνο
+ Επιλογές συνδεδεμένου υπολογιστή
+ Συνδεδεμένοι υπολογιστές
+ Συνδεδεμένα τηλέφωνα
+ Σύνδεσε τις εφαρμογές κινητού και υπολογιστή! 🔗
+ εικόνα προεπισκόπησης συνδέσμου
+ Λίστα
+ Όνομα λίστας...
+ Το όνομα της λίστας και το emoji πρέπει να είναι διαφορετικά για όλες τις λίστες.
+ Διεπαφή στα Λιθουανικά
+ ΖΩΝΤΑΝΑ
+ Ζωντανό μήνυμα!
+ Ζωντανά μηνύματα!
+ Φόρτωση συνομιλιών…
+ Φόρτωση προφίλ…
+ Φόρτωση αρχείου
+ Τοπικό όνομα
+ Μόνο τοπικά δεδομένα προφίλ
+ Κλείδωμα μετά
+ Λειτουργία κλειδώματος
+ Σύνδεση χρησιμοποιώντας τα στοιχεία σου
+ Δημιούργησε μία ιδιωτική συνομιλία
+ Εξαφάνισε ένα μήνυμα
+ Κάνε το προφίλ ιδιωτικό!
+ Βεβαιώσου ότι έχεις σωστή διαμόρφωση του διακομιστή μεσολάβησης.
+ Βεβαιώσου ότι οι διευθύνσεις του διακομιστή SMP έχουν σωστή μορφή, διαχωρίζονται με νέα γραμμή και δεν είναι διπλότυπες.
+ Βεβαιώσου ότι το αρχείο έχει σωστή σύνταξη YAML. Κάνε εξαγωγή ενός θέματος για να έχεις παράδειγμα της δομής αρχείου των θεμάτων.
+ "Βεβαιώσου ότι οι διευθύνσεις των διακομιστών WebRTC ICE έχουν σωστή μορφή, διαχωρίζονται με νέα γραμμή και δεν είναι διπλότυπες."
+ Βεβαιώσου ότι οι διευθύνσεις των διακομιστών XFTP έχουν σωστή μορφή, διαχωρίζονται με νέα γραμμή και δεν είναι διπλότυπες.
+ Κάνε τις συνομιλίες σου να ξεχωρίζουν!
+ Βοήθεια στη Markdown σύνταξη
+ Σύνταξη Markdown στα μηνύματα
+ Επισήμανση ως αναγνωσμένο
+ Επισήμανση ως μη αναγνωσμένο
+ Επισήμανση ως επαληθευμένο
+ Μέχρι 40 δευτερόλεπτα, λαμβάνεται άμεσα.
+ Διακομιστές πολυμέσων & αρχείων
+ Μεσαίο
+ μέλος
+ ΜΕΛΟΣ
+ Μέλος %1$s
+ το μέλος %1$s άλλαξε σε %2$s
+ Εγγραφή μέλους
+ το μέλος έχει παλαιότερη έκδοση
+ Το μέλος είναι ανενεργό
+ Το μέλος διαγράφηκε – δεν μπορεί να γίνει αποδοχή του αιτήματος
+ Τα μηνύματα του μέλους θα διαγραφούν – αυτό δεν μπορεί να αναιρεθεί!
+ Αναφορές μέλους
+ Τα μέλη μπορούν να προσθέτουν αντιδράσεις στα μηνύματα.
+ Τα μέλη μπορούν να διαγράψουν οριστικά τα απεσταλμένα μηνύματα. (24 ώρες)
+ Τα μέλη μπορούν να αναφέρουν μηνύματα στους διαχειριστές.
+ Τα μέλη μπορούν να στέλνουν απευθείας μηνύματα.
+ Τα μέλη μπορούν να στέλνουν μηνύματα που εξαφανίζονται.
+ Τα μέλη μπορούν να στέλνουν αρχεία και πολυμέσα.
+ Τα μέλη μπορούν να στέλνουν συνδέσμους SimpleX.
+ Τα μέλη μπορούν να στέλνουν φωνητικά μηνύματα.
+ Τα μέλη θα αφαιρεθούν από τη συνομιλία – αυτό δεν μπορεί να αναιρεθεί!
+ Τα μέλη θα αφαιρεθούν από τη ομάδα – αυτό δεν μπορεί να αναιρεθεί!
+ Το μέλος θα αφαιρεθεί από τη συνομιλία – αυτό δεν μπορεί να αναιρεθεί!
+ Το μέλος θα αφαιρεθεί από την ομάδα – αυτό δεν μπορεί να αναιρεθεί!
+ Το μέλος θα συμμετάσχει στην ομάδα, να γίνει αποδοχή του;
+ Επισήμανση μελών 👋
+ Μενού & προειδοποιήσεις
+ μήνυμα
+ Μήνυμα
+ Σφάλμα παράδοσης μηνύματος
+ Αναφορές παράδοσης μηνύματος!
+ Προειδοποίηση παράδοσης μηνύματος
+ Πρόχειρο μήνυμα
+ Πρόχειρο μήνυμα
+ Το μήνυμα προωθήθηκε
+ Στείλε μήνυμα αμέσως μόλις πατήσεις Σύνδεση.
+ Το μήνυμα είναι πολύ μεγάλο!
+ Το μήνυμα μπορεί να παραδοθεί αργότερα όταν το μέλος γίνει ενεργό.
+ Πληροφορίες ουράς μηνυμάτων
+ Αντιδράσεις μηνυμάτων
+ Αντιδράσεις μηνυμάτων
+ Απαγορεύονται οι αντιδράσεις στα μηνύματα.
+ Απαγορεύονται οι αντιδράσεις στα μηνύματα σε αυτήν τη συνομιλία.
+ Λήψη μηνυμάτων
+ Εναλλακτική δρομολόγηση μηνυμάτων
+ Λειτουργία δρομολόγησης μηνυμάτων
+ Μηνύματα
+ ΜΗΝΥΜΑΤΑ ΚΑΙ ΑΡΧΕΙΑ
+ Διακομιστές μηνυμάτων
+ Θα εμφανιστούν τα μηνύματα από το %s!
+ Θα εμφανιστούν τα μηνύματα από αυτά τα μέλη!
+ Μορφή μηνύματος
+ Τα μηνύματα σε αυτήν τη συνομιλία δεν θα διαγραφούν ποτέ.
+ Η πηγή του μηνύματος παραμένει ιδιωτική.
+ Ληφθέντα μηνύματα
+ Απεσταλμένα μηνύματα
+ Κατάσταση μηνύματος
+ Κατάσταση μηνύματος: %s
+ Τα μηνύματα διαγράφηκαν αφού τα επιλέξατε.
+ Τα μηνύματα θα διαγραφούν - αυτό δεν μπορεί να αναιρεθεί!
+ Τα μηνύματα θα επισημανθούν για διαγραφή. Ο/Οι παραλήπτης/ες θα μπορούν να αποκαλύψουν αυτά τα μηνύματα.
+ Κείμενο μηνύματος
+ Το μήνυμα είναι πολύ μεγάλο
+ Το μήνυμα θα διαγραφεί - αυτό δεν μπορεί να αναιρεθεί!
+ Το μήνυμα θα επισημανθεί για διαγραφή. Ο/Οι παραλήπτης/ες θα μπορούν να αποκαλύψουν αυτό το μήνυμα.
+ Μικρόφωνο
+ Μετεγκατάσταση συσκευής
+ Μετεγκατάσταση από άλλη συσκευή
+ Μετεγκατάσταση εδώ
+ Μετεγκατάσταση σε άλλη συσκευή
+ Μετεγκατάσταση σε άλλη συσκευή μέσω QR κωδικού.
+ Μετεγκατάσταση σε εξέλιξη
+ Η μετεγκατάσταση ολοκληρώθηκε
+ Μετεγκαταστάσεις: %s
+ λεπτά
+ αναπάντητη κλήση
+ Αναπάντητη κλήση
+ Διαχειρίσου
+ διαχειρίζεται
+ Διαχειρίστηκε στις
+ Διαχειρίστηκε στις: %s
+ διαχειριστής
+ διαχειριστές
+ μήνες
+ Περισσότερα
+ Σύντομα έρχονται περισσότερες βελτιώσεις!
+ Σύντομα έρχονται περισσότερες βελτιώσεις!
+ Πιο αξιόπιστη σύνδεση δικτύου.
+ - πιο σταθερή παράδοση μηνυμάτων.\n- λίγο καλύτερες ομάδες.\n- και πολλά ακόμα!
+ Πιθανότατα αυτή η επαφή να έχει διαγράψει τη σύνδεση μαζί σου.
+ Πολλαπλά προφίλ συνομιλίας
+ Σίγαση
+ Σίγαση
+ Σίγαση όλων
+ Σε σίγαση όταν είναι ανενεργό!
+ Σύνδεση δικτύου
+ Αποκέντρωση δικτύου
+ Προβλήματα δικτύου - το μήνυμα έληξε μετά από πολλές προσπάθειες αποστολής.
+ Διαχείριση δικτύου
+ Χειριστής δικτύου
+ Χειριστές δικτύου
+ Δίκτυο & διακομιστές
+ Κατάσταση δικτύου
+ ποτέ
+ Ποτέ
+ Νέα συνομιλία
+ Νέα εμπειρία συνομιλίας 🎉
+ Νέα θέματα συνομιλίας
+ Νέο αίτημα επαφής
+ Νέο αρχείο βάσης δεδομένων
+ Νέα εφαρμογή για υπολογιστές!
+ Νέο εμφανιζόμενο όνομα:
+ δευτερόλεπτα
+ Το βιογραφικό σου:
+ Πάτα Σύνδεση για να συνομιλήσεις
+ Πάτα Σύνδεση για αποστολή αιτήματος
+ Πάτα Σύνδεση για να χρησιμοποιήσεις το μποτ
+ Πατήστε Δημιουργία διεύθυνσης SimpleX στο μενού, για να τη δημιουργήσετε αργότερα.
+ Πάτα Συμμετοχή στην ομάδα
+ Πάτα για να ενεργοποιήσεις το προφίλ.
+ Πάτα για Σύνδεση
+ Πάτα για συμμετοχή
+ Πάτα για ανώνυμη συμμετοχή
+ Πάτα για επικόλληση συνδέσμου
+ Πάτα για σάρωση
+ Πάτα για να ξεκινήσεις μία νέα συνομιλία
+ Σύνδεση TCP
+ Χρόνος λήξης σύνδεσης TCP στο παρασκήνιο
+ Χρόνος λήξης σύνδεσης TCP
+ Θύρα TCP για ανταλλαγή μηνυμάτων
+ Σφάλμα προσωρινού αρχείου
+ Η δοκιμή απέτυχε στο βήμα %s.
+ Δοκιμή διακομιστή
+ Δοκιμή διακομιστών
+ Ευχαριστούμε τους χρήστες – συνεισφέρετε μέσω του Weblate!
+ Ευχαριστούμε τους χρήστες – συνεισφέρετε μέσω του Weblate!
+ Ευχαριστούμε τους χρήστες – συνεισφέρετε μέσω του Weblate!
+ Ευχαριστούμε τους χρήστες – συνεισφέρετε μέσω του Weblate!
+ Ευχαριστούμε τους χρήστες – συνεισφέρετε μέσω του Weblate!
+ Σε ευχαριστούμε που εγκατέστησες το SimpleX Chat!
+ Η διεύθυνση θα είναι σύντομη και το προφίλ σου θα κοινοποιηθεί μέσω αυτής.
+ Η εφαρμογή λαμβάνει νέα μηνύματα περιοδικά — καταναλώνει ένα μικρό ποσοστό της μπαταρίας ανά ημέρα. Η εφαρμογή δεν χρησιμοποιεί ειδοποιήσεις push — τα δεδομένα από τη συσκευή σου δεν αποστέλλονται στους διακομιστές.
+ Η εφαρμογή ενδέχεται να κλείσει μετά από 1 λεπτό στο παρασκήνιο.
+ Η εφαρμογή προστατεύει το απόρρητό σου χρησιμοποιώντας διαφορετικούς χειριστές σε κάθε συνομιλία.
+ Η εφαρμογή θα ζητήσει επιβεβαίωση για λήψεις από άγνωστους διακομιστές αρχείων (εκτός από .onion ή όταν είναι ενεργοποιημένος ο διακομιστής μεσολάβησης SOCKS).
+ Η προσπάθεια αλλαγής της φράσης πρόσβασης της βάσης δεδομένων δεν ολοκληρώθηκε.
+ Ο κωδικός που σάρωσες δεν είναι κωδικός QR ενός συνδέσμου SimpleX.
+ Η σύνδεση έφτασε στο όριο των μη παραδοθέντων μηνυμάτων, η επαφή σου ενδέχεται να είναι εκτός σύνδεσης.
+ Η σύνδεση που αποδέχθηκες θα ακυρωθεί!
+ Η επαφή με την οποία μοιράστηκες αυτόν το σύνδεσμο, ΔΕΝ θα μπορεί να συνδεθεί!
+ Η βάση δεδομένων δεν λειτουργεί σωστά. Πάτησε για να μάθεις περισσότερα.
+ Για τις κλήσεις απαιτείται ο προεπιλεγμένος περιηγητής. Ρύθμισε τον προεπιλεγμένο περιηγητή στο σύστημα σου και μοιράσου περισσότερες πληροφορίες με τους προγραμματιστές.
+ Το όνομα της συσκευής θα κοινοποιηθεί στην εφαρμογή του συνδεδεμένου κινητού.
+ Η κρυπτογράφηση λειτουργεί και η νέα κρυπτογράφηση δεν είναι απαραίτητη. Μπορεί να προκαλέσει σφάλματα σύνδεσης!
+ Το μέλλον στην ανταλλαγή μηνυμάτων
+ Ο κωδικός ελέγχου του προηγούμενου μηνύματος είναι διαφορετικός.
+ Ο αναγνωριστικός κωδικός του επόμενου μηνύματος είναι λανθασμένος (μικρότερος ή ίσος με τον προηγούμενο).\nΑυτό μπορεί να συμβεί λόγω κάποιου σφάλματος ή όταν η σύνδεση έχει παραβιαστεί.
+ Η εικόνα δεν μπορεί να αποκωδικοποιηθεί. Δοκίμασε μια άλλη εικόνα ή επικοινώνησε με τους προγραμματιστές.
+ Ο σύνδεσμος θα είναι σύντομος και το προφίλ της ομάδας θα κοινοποιηθεί μέσω αυτού.
+ Θέμα
+ ΘΕΜΑΤΑ
+ Τα μηνύματα θα διαγραφούν για όλα τα μέλη.
+ Τα μηνύματα θα επισημαίνονται ως ελεγχόμενα για όλα τα μέλη.
+ Το μήνυμα θα διαγραφεί για όλα τα μέλη.
+ Το μήνυμα θα επισημανθεί ως υπό έλεγχο για όλα τα μέλη.
+ Η πλατφόρμα μηνυμάτων και εφαρμογών που προστατεύει το απόρρητο και την ασφάλειά σου.
+ Η φράση πρόσβασης αποθηκεύεται στις ρυθμίσεις ως απλό κείμενο.
+ Η φράση πρόσβασης θα αποθηκευτεί στις ρυθμίσεις ως απλό κείμενο μετά την αλλαγή της ή την επανεκκίνηση της εφαρμογής.
+ Το προφίλ κοινοποιείται μόνο στις επαφές σου.
+ Η αναφορά θα αρχειοθετηθεί για εσένα.
+ Ο ρόλος θα αλλάξει σε %s. Όλοι οι συμμετέχοντες στη συνομιλία θα ειδοποιηθούν.
+ Ο ρόλος θα αλλάξει σε %s. Όλα τα μέλη της ομάδας θα ενημερωθούν.
+ Ο ρόλος θα αλλάξει σε %s. Το μέλος θα λάβει νέα πρόσκληση.
+ Ο δεύτερος προκαθορισμένος χειριστής στην εφαρμογή!
+ Το δεύτερο τικ που χάσαμε! ✅
+ Ο αποστολέας ΔΕΝ θα ειδοποιηθεί.
+ Οι διακομιστές για τις νέες συνδέσεις του τρέχοντος προφίλ συνομιλίας σου
+ Οι διακομιστές για τα νέα αρχεία του τρέχοντος προφίλ συνομιλίας σου
+ Αυτές οι ρυθμίσεις ισχύουν για το τρέχον προφίλ σου
+ Το κείμενο που επικόλλησες δεν είναι σύνδεσμος SimpleX.
+ Το αρχείο της βάσης δεδομένων που μεταφορτώθηκε, θα διαγραφεί οριστικά από τους διακομιστές.
+ Το βίντεο δεν μπορεί να αποκωδικοποιηθεί. Δοκίμασε ένα άλλο βίντεο ή επικοινώνησε με τους προγραμματιστές.
+ Μπορούν να παρακαμφθούν στις ρυθμίσεις επαφών και ομάδων.
+ Αυτή η ενέργεια δεν μπορεί να αναιρεθεί - όλα τα ληφθέντα και απεσταλμένα αρχεία και πολυμέσα θα διαγραφούν. Οι εικόνες χαμηλής ανάλυσης θα παραμείνουν.
+ Αυτή η ενέργεια δεν μπορεί να αναιρεθεί - τα μηνύματα που έχουν αποσταλεί και παραληφθεί πριν από την επιλεγμένη ημερομηνία, θα διαγραφούν. Η διαδικασία μπορεί να διαρκέσει αρκετά λεπτά.
+ Αυτή η ενέργεια δεν μπορεί να αναιρεθεί - τα μηνύματα που έχουν αποσταλεί και παραληφθεί σε αυτήν τη συνομιλία, πριν από την επιλεγμένη ημερομηνία, θα διαγραφούν.
+ Αυτή η ενέργεια δεν μπορεί να αναιρεθεί - το προφίλ, οι επαφές, τα μηνύματα και τα αρχεία σου, θα χαθούν οριστικά και ανεπανόρθωτα.
+ Αυτή η συνομιλία προστατεύεται με κρυπτογράφηση από άκρη-σε-άκρη.
+ Αυτή η συνομιλία προστατεύεται με κβαντο-ανθεκτική κρυπτογράφηση από άκρη-σε-άκρη.
+ Αυτή η συσκευή
+ Το όνομα αυτής της συσκευής
+ Το εμφανιζόμενο όνομα δεν είναι έγκυρο. Επέλεξε ένα άλλο όνομα.
+ Αυτή η λειτουργία δεν υποστηρίζεται ακόμη. Δικίμασε την επόμενη έκδοση.
+ Αυτή η ομάδα έχει πάνω από %1$d μέλη, δεν αποστέλλονται αναφορές παράδοσης.
+ Αυτή η ομάδα δεν υπάρχει πλέον.
+ Αυτός είναι ο δικός σου σύνδεσμος 1-χρήσης!
+ Αυτή είναι η διεύθυνση σου SimpeX!
+ Αυτός ο σύνδεσμος δεν είναι έγκυρος!
+ Αυτός ο σύνδεσμος απαιτεί νεότερη έκδοση της εφαρμογής. Αναβάθμισε την εφαρμογή ή ζήτησε από την επαφή σου να σου στείλει ένα συμβατό σύνδεσμο.
+ Αυτός ο σύνδεσμος χρησιμοποιήθηκε με άλλη κινητή συσκευή. Δημιούργησε ένα νέο σύνδεσμο στον υπολογιστή σου.
+ Αυτό το μήνυμα διαγράφηκε ή δεν έχει ληφθεί ακόμα.
+ Αυτός ο κωδικός QR δεν είναι σύνδεσμος!
+ Αυτή η ρύθμιση ισχύει για τα μηνύματα στο τρέχον προφίλ συνομιλίας σου.
+ Αυτή η ρύθμιση αφορά το τρέχον προφίλ σου.
+ Αυτό το κείμενο δεν είναι σύνδεσμος!
+ Αυτό το κείμενο είναι διαθέσιμο στις ρυθμίσεις
+ Εξαντλήθηκε ο χρόνος αναμονής κατά τη σύνδεση με τον υπολογιστή
+ Ο χρόνος εξαφάνισης ορίζεται μόνο για τις νέες επαφές.
+ Τίτλος
+ Για να επιτρέψεις σε μια εφαρμογή κινητού να συνδεθεί στον υπολογιστή, άνοιξε αυτήν τη θύρα στο τείχος προστασίας σου, εάν το έχεις ενεργοποιήσει.
+ Για να λαμβάνεις ειδοποιήσεις σχετικά με τις νέες εκδόσεις, ενεργοποίησε τον περιοδικό έλεγχο για σταθερές ή δοκιμαστικές εκδόσεις.
+ Για να συνδεθείς μέσω συνδέσμου
+ Για να συνδεθείς, η επαφή σου μπορεί να σαρώσει τον κωδικό QR ή να χρησιμοποιήσει τον σύνδεσμο στην εφαρμογή.
+ Εναλλαγή λίστας συνομιλιών:
+ Ενεργοποίηση ανώνυμης λειτουργίας κατά τη σύνδεση.
+ Για απόκρυψη ανεπιθύμητων μηνυμάτων.
+ Για να πραγματοποιήσεις κλήσεις, επέτρεψε τη χρήση του μικροφώνου σου. Τερμάτισε την κλήση και προσπάθησε να καλέσεις ξανά.
+ Πάρα πολλές εικόνες!
+ Πάρα πολλά βίντεο!
+ Για να προστατευτείς από αντικατάσταση του συνδέσμου σου, μπορείς να συγκρίνεις τους κωδικούς ασφαλείας των επαφών σου.
+ Για την προστασία της ζώνης ώρας, τα αρχεία εικόνας/φωνής χρησιμοποιούν UTC ώρα.
+ Για να προστατεύσεις τις πληροφορίες σου, ενεργοποίησε το SimpleX Lock.\nΘα σου ζητηθεί να ολοκληρώσεις την επαλήθευση ταυτότητας πριν ενεργοποιηθεί αυτή η λειτουργία.
+ Για την προστασία της IP διεύθυνσής σου, η ιδιωτική δρομολόγηση χρησιμοποιεί τους διακομιστές SMP για την παράδοση μηνυμάτων.
+ Για την προστασία της ιδιωτικότητάς σου, το SimpleX χρησιμοποιεί ξεχωριστά αναγνωριστικά για κάθε μία από τις επαφές σου.
+ Για λήψη
+ Για να λαμβάνεις ειδοποιήσεις, παρακαλώ εισήγαγε τη φράση πρόσβασης της βάσης δεδομένων.
+ Για να αποκαλύψεις το κρυφό προφίλ σου, εισήγαγε έναν πλήρη κωδικό στο πεδίο αναζήτησης στη σελίδα Τα προφίλ συνομιλίας σου.
+ Για αποστολή
+ Για αποστολή εντολών, θα πρέπει να είσαι συνδεδεμένος.
+ (για διαμοιρασμό με την επαφή σου)
+ Για να ξεκινήσεις μία νέα συνομιλία
+ Συνολικά
+ Για να χρησιμοποιήσεις άλλο προφίλ μετά την προσπάθεια σύνδεσης, διέγραψε τη συνομιλία και χρησιμοποίησε ξανά τον σύνδεσμο.
+ Για να επαληθεύσεις την κρυπτογράφηση από άκρη-σε-άκρη με την επαφή σου, συγκρίνετε (ή σαρώστε) τον κωδικό στις συσκευές σας.
+ Διαφάνεια
+ Απομόνωση μεταφοράς
+ Απομόνωση μεταφοράς
+ Μεταφορές συνεδριών
+ Ενεργοποίηση
+ μη εξουσιοδοτημένη αποστολή
+ Ξεμπλοκάρισμα
+ ξεμπλοκαρισμένο %s
+ Ξεμπλοκάρισμα για όλους
+ Ξεμπλοκάρισμα μέλους
+ Ξεμπλοκάρισμα μέλους;
+ Ξεμπλοκάρισμα μέλους για όλους;
+ Ξεμπλοκάρισμα μελών για όλους;
+ Μηνύματα που δεν παραδόθηκαν
+ Αφαίρεση από τα αγαπημένα
+ Εμφάνιση
+ Εμφάνιση προφίλ συνομιλίας
+ Εμφάνιση προφίλ
+ άγνωστο
+ Άγνωστο σφάλμα βάσης δεδομένων: %s
+ Άγνωστο σφάλμα
+ άγνωστη μορφή μηνύματος
+ Άγνωστοι διακομιστές
+ Άγνωστοι διακομιστές!
+ άγνωστη κατάσταση
+ Εκτός αν η επαφή σου διέγραψε τη σύνδεση ή αυτός ο σύνδεσμος είχε ήδη χρησιμοποιηθεί, μπορεί να πρόκειται για σφάλμα - παρακαλούμε να το αναφέρεις.\nΓια να συνδεθείς, ζήτησε από την επαφή σου να δημιουργήσει έναν άλλο σύνδεσμο σύνδεσης και έλεγξε ότι έχεις σταθερή σύνδεση δικτύου.
+ Αποσύνδεση
+ Αποσύνδεση υπολογιστή;
+ Ξεκλείδωμα
+ Απενεργοποίηση σίγασης
+ Απενεργοποίηση σίγασης
+ Απροστάτευτο
+ αδιάβαστο
+ Μη αναγνωσμένες αναφορές
+ Μη υποστηριζόμενος σύνδεσμος σύνδεσης
+ Αναβάθμιση
+ Αναβάθμιση
+ Αναβάθμιση
+ Διαθέσιμη αναβάθμιση: %s
+ Ενημέρωση φράσης πρόσβασης της βάσης δεδομένων
+ Ενημερωμένοι όροι
+ ενημερωμένο προφίλ ομάδας
+ Η λήψη της ενημέρωσης ακυρώθηκε
+ ενημερωμένο προφίλ
+ Ενημέρωση ρυθμίσεων δικτύου;
+ Ενημέρωση της λειτουργίας απομόνωσης μεταφοράς;
+ Ενημέρωσε τη διεύθυνσή σου
+ Η ενημέρωση των ρυθμίσεων θα επανασυνδέσει την εφαρμογή με όλους τους διακομιστές.
+ Αναβάθμιση
+ Αναβάθμιση διεύθυνσης
+ Αναβάθμιση διεύθυνσης;
+ Αναβάθμιση και άνοιγμα συνομιλίας
+ Αυτόματη αναβάθμιση εφαρμογής
+ Αναβάθμιση συνδέσμου ομάδας
+ Αναβάθμιση συνδέσμου ομάδας;
+ Ανέβηκε
+ Ανεβασμένα αρχεία
+ Σφάλματα μεταφόρτωσης
+ Αποτυχία μεταφόρτωσης
+ Ανέβασμα αρχείου
+ Ανεβαίνει το αρχείο αρχειοθέτησης
+ Τα τελευταία 100 μηνύματα αποστέλλονται στα νέα μέλη.
+ Χρήση συνομιλίας
+ Χρησιμοποίησε διαφορετικά διαπιστευτήρια διακομιστή μεσολάβησης για κάθε σύνδεση.
+ Χρησιμοποίησε διαφορετικά διαπιστευτήρια διακομιστή μεσολάβησης για κάθε προφίλ.
+ Χρήση απευθείας σύνδεσης στο Διαδίκτυο;
+ Χρήση για αρχεία
+ Χρήση για μηνύματα
+ Χρήση για νέες συνδέσεις
+ Χρήση από τον υπολογιστή
+ Χρήση ανώνυμου προφίλ
+ Χρήση κεωτρικών διακομιστών .onion
+ Χρήση ιδιωτικής δρομολόγησης με άγνωστους διακομιστές.
+ Χρήση ιδιωτικής δρομολόγησης με άγνωστους διακομιστές όταν η διεύθυνση IP δεν προστατεύεται.
+ Χρήση τυχαίων διαπιστευτηρίων
+ Χρήση τυχαίας φράσης πρόσβασης
+ Όνομα χρήστη
+ Χρήση %s
+ Χρήση διακομιστή
+ Χρήση διακομιστών
+ Χρήση διακομιστών SimpleX Chat;
+ Χρήση δικομιστή μεσολάβησης SOCKS
+ Χρήση διακομιστή μεσολάβησης SOCKS;
+ Χρήση της θύρας TCP %1$s όταν δεν έχει καθοριστεί θύρα.
+ Χρήση της θύρας TCP 443 μόνο για προκαθορισμένους διακομιστές.
+ Χρήση της εφαρμογής κατά τη διάρκεια μίας κλήσης.
+ Χρήση της εφαρμογής με το ένα χέρι
+ Χρήση θύρας web
+ Χρήση διακομιστών SimpleX Chat.
+ Σφάλμα κατά την προσθήκη μέλους/ων
+ Σφάλμα κατά την προσθήκη διακομιστή
+ Σφάλμα κατά το μπλοκάρισμα του μέλους, για όλους
+ Σφάλμα κατά την αλλαγή διεύθυνσης
+ Σφάλμα κατά την αλλαγή προφίλ
+ Σφάλμα κατά την αλλαγή ρόλου
+ Σφάλμα κατά την αλλαγή της ρύθμισης
+ Σφάλμα κατά τη σύνδεση με το διακομιστή προώθησης %1$s. Παρακαλώ δοκίμασε ξανά αργότερα.
+ Σφάλμα κατά τη σύνδεση με τον διακομιστή που χρησιμοποιείται για τη λήψη μηνυμάτων από αυτή τη σύνδεση: %1$s.
+ Σφάλμα κατά τη δημιουργία διεύθυνσης
+ Σφάλμα κατά τη δημιουργία της λίστας συνομιλιών
+ Σφάλμα κατά τη δημιουργία συνδέσμου ομάδας
+ Σφάλμα κατά τη δημιουργία επαφής μέλους
+ Σφάλμα κατά τη δημιουργία μηνύματος
+ Σφάλμα κατά τη δημιουργία προφίλ!
+ Σφάλμα κατά τη δημιουργία της αναφοράς
+ Σφάλμα κατά τη διαγραφή της συνομιλίας
+ Σφάλμα κατά τη διαγραφή της βάσης δεδομένων συνομιλιών
+ Σφάλμα κατά τη διαγραφή της επαφής
+ Σφάλμα κατά τη διαγραφή του αιτήματος της επαφής
+ Σφάλμα κατά τη διαγραφή της βάσης δεδομένων
+ Σφάλμα κατά τη διαγραφή ομάδας
+ Σφάλμα κατά τη διαγραφή του συνδέσμου ομάδας
+ Σφάλμα κατά τη διαγραφή εκκρεμούς σύνδεσης επαφής
+ Σφάλμα κατά τη διαγραφή ιδιωτικών σημειώσεων
+ Σφάλμα κατά τη διαγραφή του προφίλ χρήστη
+ Σφάλμα κατά τη λήψη του αρχείου αρχειοθέτησης
+ Σφάλμα κατά την ενεργοποίηση των αναφορών παράδοσης!
+ Σφάλμα κατά την κρυπτογράφηση της βάσης δεδομένων
+ Σφάλμα κατά την εξαγωγή της βάσης δεδομένων συνομιλιών
+ Σφάλμα κατά την εξαγωγή της βάσης δεδομένων συνομιλιών
+ Σφάλμα προώθησης μηνυμάτων
+ Σφάλμα κατά την εισαγωγή της βάσης δεδομένων συνομιλιών
+ Σφάλμα κατά την αρχικοποίηση του WebView. Βεβαιώσου ότι έχεις εγκαταστήσει το WebView και ότι η υποστηριζόμενη αρχιτεκτονική είναι arm64.\nΣφάλμα: %s
+ Σφάλμα κατά την αρχικοποίηση του WebView. Ενημέρωσε το σύστημά σου στη νέα έκδοση. Επικοινώνησε με τους προγραμματιστές.\nΣφάλμα: %s
+ Σφάλμα κατά τη συμμετοχή στην ομάδα
+ Σφάλμα κατά τη φόρτωση των λιστών συνομιλιών
+ Σφάλμα κατά τη φόρτωση των λεπτομερειών
+ Σφάλμα κατά τη φόρτωση των διακομιστών SMP
+ Σφάλμα κατά τη φόρτωση των διακομιστών XFTP
+ Σφάλμα επισήμανσης ως αναγνωσμένου
+ Σφάλμα κατά το άνοιγμα του προγράμματος περιήγησης
+ Σφάλμα κατά το άνοιγμα της συνομιλίας
+ Σφάλμα κατά το άνοιγμα της ομάδας
+ Σφάλμα κατά την ανάγνωση της φράσης πρόσβασης της βάσης δεδομένων
+ Σφάλμα κατά τη λήψη του αρχείου
+ Σφάλμα κατά την επανασύνδεση του διακομιστή
+ Σφάλμα κατά την επανασύνδεση των διακομιστών
+ Σφάλμα κατά την απόρριψη αιτήματος της επαφής
+ Σφάλμα κατά την αφαίρεση του μέλους
+ Σφάλμα επαναφοράς στατιστικών στοιχείων
+ Σφάλμα: %s
+ Σφάλματα
+ Σφάλμα κατά την αποθήκευση της βάσης δεδομένων
+ Σφάλμα κατά την αποθήκευση του αρχείου
+ Σφάλμα κατά την αποθήκευση του προφίλ ομάδας
+ Σφάλμα κατά την αποθήκευση των διακομιστών ICE
+ Σφάλμα κατά την αποθήκευση του διακομιστή μεσολάβησης
+ Σφάλμα κατά την αποθήκευση διακομιστών
+ Σφάλμα κατά την αποθήκευση των ρυθμίσεων
+ Σφάλμα κατά την αποθήκευση των ρυθμίσεων
+ Σφάλμα κατά την αποθήκευση των διακομιστών SMP
+ Σφάλμα κατά την αποθήκευση του κωδικού πρόσβασης χρήστη
+ Σφάλμα κατά την αποθήκευση διακομιστών XFTP
+ Σφάλμα κατά την αποστολή της πρόσκλησης
+ Σφάλμα κατά την αποστολή του μηνύματος
+ Σφάλμα κατά τη ρύθμιση της διεύθυνσης
+ σφάλμα κατά την εμφάνιση του περιεχομένου
+ σφάλμα εμφάνισης μηνύματος
+ Σφάλμα στην εμφάνιση της ειδοποίησης, επικοινώνησε με τους προγραμματιστές.
+ Σφάλματα στη διαμόρφωση των διακομιστών.
+ Σφάλμα κατά την έναρξη της συνομιλίας
+ Σφάλμα κατά τη διακοπή της συνομιλίας
+ Σφάλμα κατά την εναλλαγή προφίλ
+ Σφάλμα κατά την αλλαγή προφίλ!
+ Σφάλμα κατά τo συγχρονισμό της σύνδεσης
+ Σφάλμα κατά την ενημέρωση της λίστας συνομιλιών
+ Σφάλμα κατά την ενημέρωση του συνδέσμου ομάδας
+ Σφάλμα κατά την ενημέρωση της διαμόρφωσης δικτύου
+ Σφάλμα κατά την αναβάθμιση του διακομιστή
+ Σφάλμα κατά την ενημέρωση των ρυθμίσεων απορρήτου χρήστη
+ Σφάλμα κατά το ανέβασμα του αρχείου αρχειοθέτησης
+ Σφάλμα κατά την επαλήθευση της φράσης πρόσβασης:
+ Ακόμα και όταν είναι απενεργοποιημένη στη συνομιλία.
+ Η εκτέλεση της λειτουργίας διαρκεί πολύ χρόνο: %1$d δευτερόλεπτα: %2$s
+ Έξοδος χωρίς αποθήκευση
+ Επέκτεινε
+ Επέκταση επιλογής ρόλου
+ ΠΕΙΡΑΜΑΤΙΚΟ
+ Πειραματικά χαρακτηριστικά
+ έληξε
+ Εξαγωγή της βάσης δεδομένων
+ Το εξαγόμενο αρχείο δεν υπάρχει
+ Εξαγωγή θέματος
+ Αποτυχία φόρτωσης συνομιλίας
+ Αποτυχία φόρτωσης συνομιλιών
+ Γρήγορα και χωρίς αναμονή μέχρι να συνδεθεί ο αποστολέας!
+ Ταχύτερη διαγραφή ομάδων.
+ Ταχύτερη σύνδεση και πιο αξιόπιστα μηνύματα.
+ Ταχύτερη αποστολή μηνυμάτων.
+ Αγαπημένο
+ Αγαπημένα
+ Αρχείο
+ Αρχείο
+ Σφάλμα αρχείου
+ Το αρχείο έχει αποκλειστεί από το χειριστή του διακομιστή:\n%1$s.
+ Το αρχείο δεν βρέθηκε
+ Το αρχείο δεν βρέθηκε - πιθανότατα το αρχείο διαγράφηκε ή ακυρώθηκε.
+ Αρχείο: %s
+ Αρχεία
+ ΑΡΧΕΙΑ
+ Αρχεία και πολυμέσα
+ Απαγορεύονται τα αρχεία και τα πολυμέσα.
+ Τα αρχεία και τα πολυμέσα, απαγορεύονται σε αυτήν τη συνομιλία.
+ Δεν επιτρέπονται αρχεία και πολυμέσα
+ Απαγορεύονται αρχεία και πολυμέσα!
+ Το αρχείο αποθηκεύτηκε
+ Σφάλμα διακομιστή αρχείων: %1$s
+ Αρχεία & πολυμέσα
+ Κατάσταση αρχείου
+ Κατάσταση αρχείου: %s
+ Το αρχείο διαγράφηκε ή ο σύνδεσμος δεν είναι έγκυρος.
+ Το αρχείο θα διαγραφεί από τους διακομιστές.
+ Το αρχείο θα ληφθεί όταν η επαφή σου ολοκληρώσει τη μεταφόρτωσή του.
+ Το αρχείο θα ληφθεί όταν η επαφή σου είναι συνδεδεμένη, παρακαλώ περίμενε ή έλεγξε αργότερα!
+ Γέμισμα οθόνης
+ Φίλτραρε τις μη αναγνωσμένες και τις αγαπημένες συνομιλίες.
+ Ολοκλήρωση της μετεγκατάστασης
+ Ολοκλήρωσε τη μετεγκατάσταση σε άλλη συσκευή.
+ Επιτέλους, τα έχουμε! 🚀
+ Βρες τις συνομιλίες πιο γρήγορα
+ Βρες αυτήν την άδεια στις ρυθμίσεις Android και παραχώρησέ την χειροκίνητα.
+ Το αποτύπωμα στη διεύθυνση του διακομιστή προορισμού δεν ταιριάζει με το πιστοποιητικό: %1$s.
+ Το αποτύπωμα στη διεύθυνση του διακομιστή προώθησης δεν ταιριάζει με το πιστοποιητικό: %1$s.
+ Το αποτύπωμα στη διεύθυνση του διακομιστή δεν ταιριάζει με το πιστοποιητικό.
+ Το αποτύπωμα στη διεύθυνση του διακομιστή δεν ταιριάζει με το πιστοποιητικό: %1$s.
+ Προσαρμογή στην οθόνη
+ Επιδιόρθωση
+ Επιδιόρθωση
+ Επιδιόρθωση σύνδεσης
+ Νέος ρόλος ομάδας: Συντονιστής
+ Νέο στο %s
+ Επιλογές νέων πολυμέσων
+ Νέος ρόλος μέλους
+ Νέο μέλος θέλει να ενταχθεί στην ομάδα.
+ νέο μήνυμα
+ Νέο μήνυμα
+ Νέα συσκευή τηλεφώνου
+ Νέος κωδικός πρόσβασης
+ Νέα φράση πρόσβασης
+ Νέος διακομιστής
+ Κάθε φορά που εκκινείς την εφαρμογή, θα χρησιμοποιούνται νέα διαπιστευτήρια SOCKS.
+ Νέα διαπιστευτήρια SOCKS θα χρησιμοποιούνται για κάθε διακομιστή.
+ όχι
+ Όχι
+ Όχι
+ Όχι
+ Χωρίς κωδικό πρόσβασης εφαρμογής
+ Χωρίς κλήσεις στο παρασκήνιο
+ Χωρίς υπηρεσία παρασκηνίου
+ Χωρίς συνομιλίες
+ Δεν βρέθηκαν συνομιλίες
+ Δεν υπάρχουν συνομιλίες στη λίστα %s.
+ Δεν υπάρχουν συνομιλίες με μέλη
+ Δεν υπάρχει συνδεδεμένο κινητό
+ Δεν έχουν επιλεγεί επαφές
+ Δεν υπάρχουν επαφές για προσθήκη
+ Δεν υπάρχουν πληροφορίες παράδοσης
+ χωρίς λεπτομέρειες
+ Δεν υπάρχει ακόμη άμεση σύνδεση, το μήνυμα προωθείται από το διαχειριστή.
+ χωρίς κρυπτογράφηση e2e
+ Καμία φιλτραρισμένη συνομιλία
+ Καμία φιλτραρισμένη επαφή
+ Χωρίς ιστορικό
+ Δεν υπάρχουν πληροφορίες, δοκίμασε να επαναφορτώσεις
+ Χωρίς διακομιστές πολυμέσων και αρχείων.
+ Κανένα μήνυμα
+ Χωρίς διακομιστές μηνυμάτων.
+ κανένα
+ Δεν υπάρχει σύνδεση δικτύου
+ Καμία συνεδρία ιδιωτικής δρομολόγησης
+ Δεν υπάρχουν ληφθέντα ή απεσταλμένα αρχεία
+ Δεν έχει επιλεγεί συνομιλία
+ Δεν υπάρχουν διακομιστές για τη δρομολόγηση ιδιωτικών μηνυμάτων.
+ Δεν υπάρχουν διακομιστές για τη λήψη αρχείων.
+ Δεν υπάρχουν διακομιστές για τη λήψη μηνυμάτων.
+ Δεν υπάρχουν διακομιστές για την αποστολή αρχείων.
+ χωρίς συνδρομή
+ Μη συμβατό!
+ Σημειώσεις
+ χωρίς κείμενο
+ Δεν έχει επιλεγεί τίποτα
+ Δεν υπάρχει τίποτα να προωθήσεις!
+ Προεπισκόπηση ειδοποίησης
+ Ειδοποιήσεις
+ Ειδοποιήσεις και μπαταρία
+ Υπηρεσία ειδοποιήσεων
+ Οι ειδοποιήσεις θα παραδίδονται μόνο μέχρι να σταματήσει η εφαρμογή!
+ Οι ειδοποιήσεις θα σταματήσουν να λειτουργούν μέχρι να επανεκκινήσεις την εφαρμογή.
+ μη συγχρονισμένο
+ Δεν υπάρχουν μη αναγνωσμένες συνομιλίες
+ Χωρίς αναγνωριστικά χρήστη.
+ Τώρα οι διαχειριστές μπορούν:\n- να διαγράφουν τα μηνύματα των μελών.\n- να απενεργοποιούν μέλη (ρόλος παρατηρητή)
+ παρατηρητής
+ κλειστό`
+ κλειστό
+ κλειστό
+ Κλειστό
+ Κλειστή
+ προσφέρεται %s
+ προσφέρθηκε %s: %2s
+ ΟΚ
+ Παλιό αρχείο βάσης δεδομένων
+ ανοιχτό
+ Σύνδεσμος πρόσκλησης 1-χρήσης
+ Σύνδεσμος πρόσκλησης 1-χρήσης
+ Για τη σύνδεση θα απαιτηθούν διακομιστές Onion.\nΣημείωση: δεν θα μπορείς να συνδεθείς στους διακομιστές χωρίς διεύθυνση .onion.
+ Οι κεντρικοί υπολογιστές Onion θα χρησιμοποιούνται όταν είναι διαθέσιμοι.
+ Οι κεντρικοί υπολογιστές Onion δεν θα χρησιμοποιηθούν.
+ Μπορούν να σταλούν μόνο 10 εικόνες ταυτόχρονα
+ Μπορούν να σταλούν μόνο 10 βίντεο ταυτόχρονα
+ Μόνο οι ιδιοκτήτες του chat μπορούν να αλλάξουν τις προτιμήσεις.
+ Μόνο οι συσκευές αποθηκεύουν προφίλ χρηστών, επαφές, ομάδες και μηνύματα.
+ Διαγραφή μόνο της συνομιλίας
+ Μόνο οι ιδιοκτήτες ομάδων μπορούν να αλλάξουν τις προτιμήσεις της ομάδας.
+ Μόνο οι ιδιοκτήτες ομάδων μπορούν να ενεργοποιήσουν αρχεία και πολυμέσα.
+ Μόνο οι ιδιοκτήτες ομάδων μπορούν να ενεργοποιήσουν τα φωνητικά μηνύματα.
+ Μόνο μία συσκευή μπορεί να λειτουργεί ταυτόχρονα
+ Μόνο ο αποστολέας και οι διαχειριστές μπορούν να το δουν
+ (αποθηκεύεται μόνο από τα μέλη της ομάδας)
+ Μόνο εσύ και οι διαχειριστές το βλέπετε
+ Μόνο εσύ μπορείς να προσθέσεις αντιδράσεις σε μηνύματα.
+ Μόνο εσύ μπορείς να διαγράψεις οριστικά τα μηνύματα (η επαφή σου μπορεί να τα επισημάνει για διαγραφή). (24 ώρες)
+ Μόνο εσύ μπορείς να πραγματοποιήσεις κλήσεις.
+ Μόνο εσύ μπορείς να στέλνεις μηνύματα που εξαφανίζονται.
+ Μόνο εσύ μπορείς να στέλνεις αρχεία και πολυμέσα.
+ Μόνο εσύ μπορείς να στέλνεις φωνητικά μηνύματα.
+ Μόνο η επαφή σου μπορεί να προσθέσει αντιδράσεις σε μηνύματα.
+ Μόνο η επαφή σου μπορεί να διαγράψει οριστικά τα μηνύματα (μπορείς να τα επισημάνεις για διαγραφή). (24 ώρες)
+ Μόνο η επαφή σου μπορεί να πραγματοποιεί κλήσεις.
+ Μόνο η επαφή σου μπορεί να στείλει μηνύματα που εξαφανίζονται.
+ Μόνο η επαφή σου μπορεί να στείλει αρχεία και πολυμέσα.
+ Μόνο η επαφή σου μπορεί να στείλει φωνητικά μηνύματα.
+ άνοιγμα
+ Άνοιξε
+ Άνοιγμα
+ Άνοιξε τις ρυθμίσεις της εφαρμογής
+ Ανοιχτές αλλαγές
+ Άνοιγμα συνομιλίας
+ Άνοιγμα συνομιλίας
+ Άνοιγμα κονσόλας συνομιλίας
+ - Άνοιγμα συνομιλίας στο πρώτο μη αναγνωσμένο μήνυμα.\n- Μετάβαση στα αναφερόμενα μηνύματα.
+ Άνοιγμα καθαρού συνδέσμου
+ Ανοιχτές προϋποθέσεις
+ Άνοιγμα φακέλου βάσης δεδομένων
+ Άνοιγμα θέσης αρχείου
+ Άνοιγμα πλήρους συνδέσμου
+ Άνοιγμα ομάδας
+ Το άνοιγμα του συνδέσμου στον περιηγητή μπορεί να μειώσει την ιδιωτικότητα και την ασφάλεια της σύνδεσης. Οι μη αξιόπιστοι σύνδεσμοι SimpleX θα εμφανίζονται με κόκκινο χρώμα.
+ Άνοιγμα συνδέσμου
+ Άνοιγμα συνδέσμων από τη λίστα συνομιλιών
+ Άνοιξε την οθόνη μετεγκατάστασης
+ Άνοιξε νέα συνομιλία
+ Άνοιξε νέα ομάδα
+ Άνοιγμα θύρας στο τείχος προστασίας
+ Άνοιξε τις Ρυθμίσεις Safari / Ιστοσελίδες / Μικρόφωνο και στη συνέχεια επέλεξε Να επιτρέπεται για το localhost.
+ Άνοιγμα ρυθμίσεων διακομιστή
+ Άνοιγμα ρυθμίσεων
+ Άνοιξε το SimpleX Chat για να αποδεχθείς την κλήση
+ Άνοιξε για να αποδεχθείς
+ Άνοιξε για να συνδεθείς
+ Άνοιξε για να συμμετάσχεις
+ Άνοιξε για να χρησιμοποιήσεις το μποτ
+ Άνοιγμα συνδέσμου ιστού;
+ Άνοιγμα με %s
+ Χειριστής
+ Διακομιστής χειριστή
+ - προαιρετική ειδοποίηση για διεγραμμένες επαφές.\n- ονόματα προφίλ με κενά.\n- και πολλά άλλα!
+ Οργάνωσε τις συνομιλίες σε λίστες
+ Ή εισαγωγή αρχείου αρχειοθέτησης
+ Ή επικόλλησε το σύνδεσμο του αρχείου αρχειοθέτησης
+ Ή σάρωσε τον κωδικό QR
+ Ή μοιράσου με ασφάλεια αυτόν τον σύνδεσμο αρχείου
+ Ή δείξε αυτόν τον κωδικό
+ Ή για να μοιραστείς ιδιωτικά
+ άλλο
+ Άλλο
+ άλλα σφάλματα
+ Άλλοι διακομιστές SMP
+ Άλλοι διακομιστές XFTP
+ ιδιοκτήτης
+ ιδιοκτήτες
+ Κωδικός πρόσβασης
+ Ο κωδικός πρόσβασης αλλάχθηκε!
+ Εισαγωγή κωδικού πρόσβασης
+ Ο κωδικός πρόσβασης δεν έχει αλλάξει!
+ Ο κωδικός πρόσβασης έχει οριστεί!
+ Η φράση πρόσβασης στο Keystore δεν μπορεί να διαβαστεί, παρακαλώ εισήγαγέ τη χειροκίνητα. Αυτό μπορεί να συνέβη μετά από ενημέρωση του συστήματος που δεν είναι συμβατή με την εφαρμογή. Εάν δεν είναι αυτή η περίπτωση, παρακαλώ επικοινώνησε με τους προγραμματιστές.
+ Η φράση πρόσβασης στο Keystore δεν μπορεί να διαβαστεί. Αυτό μπορεί να συνέβη μετά από ενημέρωση του συστήματος που δεν είναι συμβατή με την εφαρμογή. Εάν δεν είναι αυτή η περίπτωση, επικοινώνησε με τους προγραμματιστές.
+ Απαιτείται φράση πρόσβασης
+ Ο φράση πρόσβασης δεν βρέθηκε στο Keystore, παρακαλώ εισήγαγέ τη χειροκίνητα. Αυτό μπορεί να συνέβη αν επανέφερες τα δεδομένα της εφαρμογής χρησιμοποιώντας ένα εργαλείο δημιουργίας αντιγράφων ασφαλείας. Αν δεν είναι αυτή η περίπτωση, παρακαλώ επικοινώνησε με τους προγραμματιστές.
+ Κωδικός
+ Κωδικός για εμφάνιση
+ Επικόλληση
+ Επικόλληση συνδέσμου αρχείου αρχειοθέτησης
+ Επικόλληση διεύθυνσης υπολογιστή
+ Επικόλληση συνδέσμου
+ Επικόλλησε το σύνδεσμο για να συνδεθείς!
+ Επικόλλησε το σύνδεσμο που έλαβες
+ Επικόλλησε το σύνδεσμο που έλαβες για να συνδεθείς με την επαφή σου…
+ από άκρη-σε-άκρη
+ εκκρεμής
+ Εκκρεμής
+ Εκκρεμής
+ σε αναμονή έγκρισης
+ Εκκρεμής κλήση
+ σε αναμονή για έλεγχο
+ Περιοδικά
+ Περιοδικές ειδοποιήσεις
+ Οι περιοδικές ειδοποιήσεις είναι απενεργοποιημένες!
+ Η άδεια απορρίφθηκε!
+ Διεπαφή στα Περσικά
+ Κλήσεις σε λειτουργία εικόνα-μέσα-στην-εικόνα
+ Μέτρηση PING
+ εσωτερικό PING
+ Αναπαραγωγή από τη λίστα συνομιλιών.
+ Παρακαλώ ζήτησε από την επαφή σου να ενεργοποιήσει τις κλήσεις.
+ Παρακαλώ ζήτησε από την επαφή σου να ενεργοποιήσει τα φωνητικά μηνύματα.
+ Έλεγξε ότι το κινητό και ο υπολογιστής είναι συνδεδεμένοι στο ίδιο τοπικό δίκτυο και ότι το τείχος προστασίας του υπολογιστή επιτρέπει τη σύνδεση.\nΕνημέρωσε τους προγραμματιστές για τυχόν άλλα προβλήματα.
+ Έλεγξε ότι ο σύνδεσμος SimpleX είναι σωστός.
+ Έλεγξε ότι χρησιμοποιείς το σωστό σύνδεσμο ή ζήτησε από την επαφή σου να σου στείλει έναν άλλο.
+ Έλεγξε τη σύνδεσή σου στο δίκτυο με %1$s και δοκίμασε ξανά.
+ Επιβεβαίωσε ότι οι ρυθμίσεις δικτύου είναι σωστές για αυτήν τη συσκευή.
+ Παρακαλώ επικοινώνησε με το διαχειριστή της ομάδας.
+ Εισήγαγε τη σωστή τρέχουσα φράση πρόσβασης.
+ Εισήγαγε τον προηγούμενο κωδικό μετά την επαναφορά του αντιγράφου ασφαλείας της βάσης δεδομένων. Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.
+ Μείωσε το μέγεθος του μηνύματος και απέστειλέ το ξανά.
+ Μείωσε το μέγεθος του μηνύματος ή αφαίρεσε τα αρχεία πολυμέσων και απέστειλέ το ξανά.
+ Παρακαλώ θυμήσου ή αποθήκευσε το με ασφάλεια - δεν υπάρχει τρόπος να ανακτήσεις έναν χαμένο κωδικό!
+ Παρακαλώ ανάφερέ το στους προγραμματιστές.
+ Παρακαλώ ανάφερέ το στους προγραμματιστές: \n%s
+ Παρακαλώ ανάφερέ το στους προγραμματιστές: \n%s\n\nΠροτείνεται η επανεκκίνηση της εφαρμογής.
+ Παρακαλώ επανεκκίνησε την εφαρμογή.
+ Αποθήκευσε τη φράση πρόσβασης σε ασφαλές μέρος, καθώς ΔΕΝ θα μπορείς να έχεις πρόσβαση στη συνομιλία αν τη χάσεις.
+ Αποθήκευσε τη φράση πρόσβασης σε ασφαλές μέρος, καθώς ΔΕΝ θα μπορείς να την αλλάξεις σε περίπτωση απώλειας.
+ Παρακαλώ δοκίμασε αργότερα.
+ Ενημέρωσε την εφαρμογή και επικοινώνησε με τους προγραμματιστές.
+ Παρακαλώ περίμενε μέχρι οι διαχειριστές της ομάδας να εξετάσουν το αίτημά σου για συμμετοχή στην ομάδα.
+ Παρακαλώ, περίμενε ενώ το αρχείο φορτώνεται από το συνδεδεμένο κινητό
+ Διεπαφή στα Πολωνικά
+ Μετέφερε
+ θύρα%d
+ Προετοιμασία λήψης
+ Προετοιμασία μεταφόρτωσης
+ Διατήρηση του τελευταίου πρόχειρου μηνύματος, με τα συνημμένα.
+ Προκαθορισμένος διακομιστής
+ Διεύθυνση προκαθορισμένου διακομιστή
+ Προκαθορισμένοι διακομιστές
+ Προκαθορισμένοι διακομιστές
+ Προεπισκόπηση
+ Προηγούμενοι συνδεδεμένοι διακομιστές
+ Προστασία της ιδιωτικότητας των πελατών σου.
+ Πολιτική απορρήτου και όροι χρήσης.
+ Επαναπροσδιορισμός της ιδιωτικότητας
+ Απόρρητο & ασφάλεια
+ Οι ιδιωτικές συνομιλίες, οι ομάδες και οι επαφές σου δεν είναι προσβάσιμες στους χειριστές του διακομιστή.
+ Ιδιωτικά ονόματα αρχείων
+ Ιδιωτικά ονόματα αρχείων πολυμέσων.
+ Δρομολόγηση ιδιωτικών μηνυμάτων 🚀
+ ΔΡΟΜΟΛΟΓΗΣΗ ΙΔΙΩΤΙΚΩΝ ΜΗΝΥΜΑΤΩΝ
+ Ιδιωτικές σημειώσεις
+ Ιδιωτικές σημειώσεις
+ Ιδιωτικές ειδοποιήσεις
+ Ιδιωτική δρομολόγηση
+ Σφάλμα ιδιωτικής δρομολόγησης
+ Λήξη χρονικού ορίου ιδιωτικής δρομολόγησης
+ Προφίλ και συνδέσεις διακομιστή
+ εικόνα προφίλ
+ θέση για εικόνα προφίλ
+ Εικόνες προφίλ
+ Όνομα προφίλ:
+ Κωδικός προφίλ
+ Θέμα προφίλ
+ Η ενημέρωση του προφίλ θα σταλεί στις επαφές σου.
+ Απαγόρευση κλήσεων ήχου/βίντεο.
+ Απαγόρευση της μη αναστρέψιμης διαγραφής μηνυμάτων.
+ Απαγόρευση αντιδράσεων σε μήνυμα.
+ Απαγόρευση αντιδράσεων σε μηνύματα.
+ Απαγόρευση αναφοράς μηνυμάτων στους διαχειριστές.
+ Απαγόρευση αποστολής άμεσων μηνυμάτων στα μέλη.
+ Απαγόρευση αποστολής μηνυμάτων που εξαφανίζονται.
+ Απαγόρευση αποστολής μηνυμάτων που εξαφανίζονται.
+ Απαγόρευση αποστολής αρχείων και πολυμέσων.
+ Απαγόρευση αποστολής αρχείων και πολυμέσων.
+ Απαγόρευση αποστολής συνδέσμων SimpleX
+ Απαγόρευση αποστολής φωνητικών μηνυμάτων.
+ Απαγόρευση αποστολής φωνητικών μηνυμάτων.
+ Προστασία οθόνης εφαρμογής
+ Προστασία διεύθυνσης IP
+ Προστάτεψε τα προφίλ συνομιλίας σου με έναν κωδικό!
+ Προστάτεψε τη διεύθυνση IP σου από τα κέντρα διαβίβασης μηνυμάτων που επιλέγουν οι επαφές σου.\nΕνεργοποίησε την επιλογή στις ρυθμίσεις *Δίκτυο και διακομιστές*.
+ Χρονικό όριο πρωτοκόλλου
+ Χρονικό όριο πρωτοκόλλου
+ Χρονικό όριο πρωτοκόλλου ανά KB
+ Μέσω διακομιστή μεσολάβησης
+ Διακομιστές μέσω proxy
+ Πιστοποίηση διακομιστή μεσολάβησης
+ Κωδικός QR
+ κβαντο-ανθεκτική κρυπτογράφηση e2e
+ Κβαντο-ανθεκτική κρυπτογράφηση
+ Τυχαία
+ Η τυχαία φράση πρόσβασης αποθηκεύεται στις ρυθμίσεις ως απλό κείμενο.\nΜπορείς να την αλλάξεις αργότερα.
+ Αξιολόγησε την εφαρμογή
+ Προσβάσιμες γραμμές εργαλείων εφαρμογής
+ Προσβάσιμη γραμμή εργαλείων συνομιλίας
+ Προσβάσιμη γραμμή εργαλείων συνομιλίας
+ Διάβασε περισσότερα
+ Οι αναφορές παράδοσης είναι απενεργοποιημένες
+ απάντηση που παραλήφθηκε…
+ Παραλήφθηκε στις
+ Παραλήφθηκε στις: %s
+ επιβεβαίωση που παραλήφθηκε…
+ Μήνυμα που παραλήφθηκε
+ Μήνυμα που παραλήφθηκε
+ Μηνύματα που παραλήφθηκαν
+ παραλήφθηκε, απαγορεύεται
+ Παραλήφθηκε απάντηση
+ Σύνολο που παραλήφθηκε
+ Σφάλματα παραλαβής
+ Η διεύθυνση παραλαβής θα αλλάξει σε διαφορετικό διακομιστή. Η αλλαγή διεύθυνσης θα ολοκληρωθεί μετά την σύνδεση του αποστολέα.
+ Λήψη ταυτόχρονης πρόσβασης
+ η λήψη αρχείων δεν υποστηρίζεται ακόμη
+ Η λήψη αρχείων θα διακοπεί.
+ Λήψη μηνυμάτων…
+ Λήψη μέσω
+ Πρόσφατο ιστορικό και βελτιωμένο μποτ καταλόγου.
+ Ο/Οι παραλήπτης/ες δεν μπορούν να δουν από ποιον προέρχεται αυτό το μήνυμα.
+ Οι παραλήπτες βλέπουν τις ενημερώσεις καθώς τις πληκτρολογείς.
+ Επανασύνδεση
+ Επανασύνδεσε όλους τους συνδεδεμένους διακομιστές για να επιβάλεις την παράδοση μηνυμάτων. Χρησιμοποιεί επιπλέον κίνηση.
+ Επανασύνδεση όλων των διακομιστών
+ Επανασύνδεση διακομιστή;
+ Επανασύνδεση διακομιστών;
+ Επανασύνδεση διακομιστή για να επιβληθεί η παράδοση μηνυμάτων. Χρησιμοποιεί επιπλέον κίνηση.
+ Η εγγραφή ενημερώθηκε στις
+ Η εγγραφή ενημερώθηκε στις: %s
+ Εγγραφή φωνητικού μηνύματος
+ Μειωμένη χρήση μπαταρίας
+ Ανανέωση
+ Απόρριψη
+ Απόρριψη
+ Απόρριψη
+ Απόρριψη αιτήματος επαφής
+ απορρίφθηκε
+ απορρίφθηκε
+ απορριφθείσα κλήση
+ Απορριφθείσα κλήση
+ Απόρριψη μέλους;
+ Ο διακομιστής αναμετάδοσης χρησιμοποιείται μόνο αν είναι απαραίτητο. Οι άλλοι μπορούν να δουν τη διεύθυνση IP σου.
+ Ο διακομιστής αναμετάδοσης προστατεύει τη διεύθυνση IP σου, αλλά μπορεί να παρακολουθεί τη διάρκεια της κλήσης.
+ Υπενθύμιση αργότερα
+ Απομακρυσμένα κινητά τηλέφωνα
+ Κατάργηση
+ Κατάργηση
+ Κατάργηση και διαγραφή μηνυμάτων
+ Κατάργηση αρχείου αρχειοθέτησης;
+ καταργήθηκε
+ καταργήθηκε %1$s
+ διεγραμμένη διεύθυνση επαφής
+ αφαιρέθηκε από την ομάδα
+ αφαιρέθηκε η φωτογραφία προφίλ
+ σε αφαίρεσε
+ Κατάργηση εικόνας
+ Κατάργηση παρακολούθησης συνδέσμων
+ Κατάργηση μέλους
+ Κατάργηση μέλους
+ Κατάργηση μέλους;
+ Κατάργηση μελών;
+ Κατάργηση φράσης πρόσβασης από το Keystore;
+ Κατάργηση φράσης πρόσβασης από τις ρυθμίσεις;
+ Κατάργηση μηνυμάτων και μπλοκάρισμα μελών.
+ Επαναδιαπραγμάτευση
+ Επαναδιαπραγμάτευση κρυπτογράφησης
+ Επαναδιαπραγμάτευση κρυπτογράφησης;
+ Επανάληψη στην οθόνη
+ Επανάληψη αιτήματος σύνδεσης;
+ Επανάληψη λήψης
+ Επανάληψη εισαγωγής
+ Επανάληψη αιτήματος συμμετοχής;
+ Επανάληψη μεταφόρτωσης
+ Απάντησε
+ Ανέφερε
+ Αναφορά περιεχομένου: μόνο οι διαχειριστές της ομάδας θα το δουν.
+ Η αναφορά μηνυμάτων απαγορεύεται σε αυτήν την ομάδα.
+ Αναφορά προφίλ μέλους: μόνο οι διαχειριστές της ομάδας θα το δουν.
+ Άλλη αναφορά: μόνο οι διαχειριστές της ομάδας θα το δουν.
+ Αιτία αναφοράς;
+ Αναφορά: %s
+ Αναφορές
+ Η αναφορά εστάλη στους διαχειριστές
+ Επανεκκίνηση
+ Αναφορά spam: μόνο οι διαχειριστές της ομάδας θα το δουν.
+ Αναφορά παραβίασης κανόνων: μόνο οι διαχειριστές της ομάδας θα τη δουν.
+ αιτήσου σύνδεση από την ομάδα %1$s
+ αιτήσου να συνδεθείς
+ το αίτημα αποστέλλεται
+ το αίτημα συμμετοχής απορρίφθηκε
+ Απαιτείται
+ Επανέφερε
+ Επαναφορά
+ Επαναφορά όλων των υποδείξεων
+ Επαναφορά όλων των στατιστικών
+ Επαναφορά όλων των στατιστικών;
+ Επαναφορά χρώματος
+ Επαναφορά χρωμάτων
+ Επαναφορά στο θέμα της εφαρμογής
+ Επαναφορά στις προεπιλογές
+ Επαναφορά στο θέμα χρήστη
+ Επανεκκίνηση συνομιλίας
+ Επανεκκίνησε την εφαρμογή για να δημιουργήσεις ένα νέο προφίλ συνομιλίας.
+ Επανεκκίνησε την εφαρμογή για να χρησιμοποιήσεις την εισαγώμενη βάση δεδομένων.
+ Επαναφορά
+ Επαναφορά αντιγράφου ασφαλείας βάσης δεδομένων
+ Επαναφορά αντιγράφου ασφαλείας βάσης δεδομένων;
+ Σφάλμα επαναφοράς βάσης δεδομένων
+ Επανέλαβε
+ Αποκάλυψε
+ ανασκόπηση
+ Προϋποθέσεις ελέγχου
+ ελέγχθηκε από τους διαχειριστές
+ Έλεγχος μελών ομάδας
+ Έλεγχος αργότερα
+ Έλεγχος μελών
+ Έλεγχος μελών πριν την αποδοχή τους (knocking).
+ Ανάκληση
+ Ανάκληση αρχείου
+ Ανάκληση αρχείου;
+ Ρόλος
+ ΕΚΚΙΝΗΣΗ ΣΥΝΟΜΙΛΙΑΣ
+ Εκτελείται όταν η εφαρμογή είναι ανοιχτή
+ Ασφαλής λήψη αρχείων
+ Ασφαλέστερες ομάδες
+ %s και %s
+ %s και %s συνδέθηκαν
+ %s στις %s
+ Αποθήκευσε
+ Αποθήκευση
+ Αποθήκευση
+ Αποθήκευση ρυθμίσεων εισόδου;
+ Αποθήκευση και ειδοποίηση επαφής
+ Αποθήκευση και ειδοποίηση επαφών
+ Αποθήκευση και ειδοποίηση μελών ομάδας
+ Αποθήκευση και επανασύνδεση
+ Αποθήκευση και ενημέρωση προφίλ ομάδας
+ αποθηκευμένο
+ Αποθηκευμένο
+ Αποθηκευμένο από
+ αποθηκευμένο από %s
+ Αποθηκευμένο μήνυμα
+ Οι αποθηκευμένοι διακομιστές WebRTC ICE θα αφαιρεθούν.
+ Αποθήκευση προφίλ ομάδας
+ Αποθήκευση λίστας
+ Αποθήκευση φράσης πρόσβασης και άνοιγμα συνομιλίας
+ Αποθήκευση φράσης πρόσβασης στο Keystore
+ Αποθήκευση φράσης πρόσβασης στις ρυθμίσεις
+ Αποθήκευση προτιμήσεων;
+ Αποθήκευση κωδικού προφίλ
+ Αποθήκευση διακομιστών
+ Αποθήκευση διακομιστών;
+ Αποθήκευση ρυθμίσεων;
+ Αποθήκευση ρυθμίσεων διεύθυνσης SimpleX
+ Αποθήκευση μηνύματος καλωσορίσματος;
+ Αποθήκευση %1$s μηνυμάτων
+ Κλιμάκωση στην οθόνη
+ Σάρωση κωδικού
+ Σάρωση από κινητό
+ (σάρωσε ή επικόλλησε από το πρόχειρο)
+ Σάρωση / Επικόλληση συνδέσμου
+ Σάρωσε τον κωδικό QR από τον υπολογιστή
+ %s συνδέθηκε
+ %s (τρέχον)
+ %s κατέβηκαν
+ αναζήτηση
+ Η γραμμή αναζήτησης δέχεται συνδέσμους πρόσκλησης.
+ Αναζήτηση ή επικόλληση συνδέσμου SimpleX
+ Δευτερεύων
+ Ασφαλής
+ ο κωδικός ασφαλείας άλλαξε
+ Επιλογή
+ Επέλεξε
+ Επέλεξε προφίλ συνομιλίας
+ Επέλεξε επαφές
+ Οι επιλεγμένες προτιμήσεις συνομιλίας απαγορεύουν αυτό το μήνυμα.
+ Επιλέχθηκαν %d
+ Επέλεξε τους χειριστές δικτύου που θέλεις να χρησιμοποιήσεις.
+ Αυτοκαταστροφή
+ Κωδικός αυτοκαταστροφής
+ Κωδικός αυτοκαταστροφής
+ Ο κωδικός αυτοκαταστροφής άλλαξε!
+ Ο κωδικός αυτοκαταστροφής ενεργοποιήθηκε!
+ Απέστειλε
+ Απέστειλε
+ Στείλε ένα ζωντανό μήνυμα - θα ενημερώνεται για τον παραλήπτη ή τους παραλήπτες καθώς το πληκτρολογείς.
+ Αποστολή αιτήματος επαφής;
+ ΑΠΟΣΤΟΛΗ ΑΝΑΦΟΡΩΝ ΠΑΡΑΔΟΣΗΣ ΣΕ
+ Αποστολή άμεσου μηνύματος
+ Στείλε άμεσο μήνυμα για να συνδεθείς
+ Αποστολή μηνύματος που εξαφανίζεται
+ Ο αποστολέας ακύρωσε τη μεταφορά αρχείων.
+ Ο αποστολέας ενδέχεται να έχει διαγράψει το αίτημα σύνδεσης.
+ Σφάλματα αποστολής
+ αποτυχία αποστολής
+ Η αποστολή αναφορών παράδοσης θα είναι ενεργοποιημένη για όλες τις επαφές.
+ Η αποστολή αναφορών παράδοσης θα είναι ενεργοποιημένη για όλες τις επαφές σε όλα τα ορατά προφίλ συνομιλίας.
+ η αποστολή αρχείων δεν υποστηρίζεται ακόμη
+ Η αποστολή του αρχείου θα διακοπεί.
+ Η αποστολή αναφορών είναι απενεργοποιημένη για %d επαφές
+ Η αποστολή αναφορών είναι απενεργοποιημένη για %d ομάδες
+ Η αποστολή αναφορών είναι ενεργοποιημένη για %d επαφές
+ Η αποστολή αναφορών είναι ενεργοποιημένη για %d ομάδες
+ Αποστέλλεται μέσω
+ Αποστολή προεπισκόπησης συνδέσμων
+ Αποστολή ζωντανού μηνύματος
+ Αποστολή Μηνύματος
+ Στείλε μηνύματα απευθείας όταν η διεύθυνση IP είναι προστατευμένη και ο διακομιστής σου ή ο διακομιστής προορισμού δεν υποστηρίζει ιδιωτική δρομολόγηση.
+ Στείλε μηνύματα απευθείας όταν ο διακομιστής σου ή ο διακομιστής προορισμού δεν υποστηρίζει ιδιωτική δρομολόγηση.
+ Στείλε μήνυμα για να ενεργοποιήσεις τις κλήσεις.
+ Αποστολή ιδιωτικών αναφορών
+ Στείλε ερωτήσεις και ιδέες
+ Αποστολή αναφορών
+ Αποστολή αιτήματος
+ Αποστολή αιτήματος χωρίς μήνυμα
+ αποστολή για σύνδεση
+ Αποστολή εώς και 100 τελευταίων μηνυμάτων σε νέα μέλη.
+ Στείλε μας ένα mail
+ Στείλε τα προσωπικά σου σχόλια στις ομάδες.
+ στάλθηκε
+ Στάλθηκε στις
+ Στάλθηκε στις: %s
+ Στάλθηκε απευθείας
+ Απεσταλμένο μήνυμα
+ Απεσταλμένο μήνυμα
+ Απεσταλμένα μηνύματα
+ Τα αποσταλμένα μηνύματα θα διαγραφούν μετά από καθορισμένο χρονικό διάστημα.
+ Απεσταλμένη απάντηση
+ Σύνολο απεσταλμένων
+ Αποστέλλεται στην επαφή σου μετά τη σύνδεση.
+ Αποστολή μέσω διακομιστή μεσολάβησης
+ Διακομιστής
+ Ο διακομιστής προστέθηκε στο χειριστή %s.
+ Διεύθυνση διακομιστή
+ Η διεύθυνση του διακομιστή δεν είναι συμβατή με τις ρυθμίσεις δικτύου.
+ Η διεύθυνση του διακομιστή δεν είναι συμβατή με τις ρυθμίσεις δικτύου: %1$s.
+ Ο χειριστής του διακομιστή άλλαξε.
+ Χειριστές διακομιστή
+ Αλλαγή πρωτοκόλλου διακομιστή.
+ πληροφορίες ουράς διακομιστή: %1$s\n\nτελευταίο ληφθέν μήνυμα: %2$s
+ Ο διακομιστής απαιτεί εξουσιοδότηση για τη δημιουργία ουρών, έλεγξε τον κωδικό.
+ Ο διακομιστής απαιτεί εξουσιοδότηση για ανέβασμα αρχείων, έλεγξε τον κωδικό.
+ ΔΙΑΚΟΜΙΣΤΕΣ
+ Πληροφορίες διακομιστών
+ Θα γίνει επαναφορά στα στατιστικά στοιχεία των διακομιστών - αυτή η ενέργεια δεν μπορεί να αναιρεθεί!
+ Η δοκιμή του διακομιστή απέτυχε!
+ Η έκδοση του διακομιστή δεν είναι συμβατή με τις ρυθμίσεις δικτύου.
+ Η έκδοση του διακομιστή δεν είναι συμβατή με την εφαρμογή σου: %1$s.
+ Κωδικός συνεδρίας
+ Όρισε σε 1 ημέρα
+ Όρισε το όνομα συνομιλίας…
+ Όρισε το όνομα επαφής
+ Όρισε το όνομα επαφής…
+ Όρισε τη φράση πρόσβασης της βάσης δεδομένων
+ Όρισε το προεπιλεγμένο θέμα
+ Όρισε τις προτιμήσεις ομάδας
+ Όρισέ τον αντί για την πιστοποίηση συστήματος.
+ Όρισε την εισαγωγή μέλους
+ Όρισε τη λήξη των μηνυμάτων στις συνομιλίες.
+ ορίστε νέα διεύθυνση επαφής
+ όρισε νέα εικόνα προφίλ
+ Όρισε κωδικό πρόσβασης
+ Όρισε φράση πρόσβασης
+ Όρισε φράση πρόσβασης για εξαγωγή
+ Όρισε το βιογραφικό του προφίλ και το μήνυμα καλωσορίσματος.
+ Όρισε το εμφανιζόμενο μήνυμα για τα νέα μέλη!
+ Ρυθμίσεις
+ Ρυθμίσεις
+ ΡΥΘΜΙΣΕΙΣ
+ Όρισε τη φράση πρόσβασης της βάσης δεδομένων
+ Διαμόρφωση εικόνων προφίλ
+ Διαμοίρασε
+ Διαμοίρασε το σύνδεσμο 1-χρήσης
+ Διαμοίρασε το σύνδεσμο 1-χρήσης με ένα φίλο
+ Διαμοιρασμός διεύθυνσης
+ Δημόσιος διαμοιρασμός διεύθυνσης
+ Διαμοιρασμός διεύθυνσης με τις επαφές;
+ Διαμοιρασμός αρχείου…
+ Διαμοιρασμός συνδέσμου
+ Διαμοιρασμός πολυμέσων…
+ Διαμοιρασμός μηνύματος…
+ Διαμοιρασμός παλιάς διεύθυνσης
+ Διαμοιρασμός παλιού συνδέσμου
+ Διαμοιρασμός προφίλ
+ Διαμοιρασμός διεύθυνσης SimpleX σε εφαρμογές κοινωνικής δικτύωσης.
+ Διαμοιρασμός αυτού του συνδέσμου 1-χρήσης
+ Διαμοιρασμός με τις επαφές
+ Διαμοιρασμός της διεύθυνσής σου
+ Σύντομη περιγραφή:
+ Σύντομος σύνδεσμος
+ Σύντομη διεύθυνση SimpleX
+ Εμφάνιση
+ Εμφάνιση:
+ Εμφάνιση λίστας μηνυμάτων σε νέο παράθυρο
+ Εμφάνιση κονσόλας τερματικού σε νέο παράθυρο
+ Εμφάνιση επαφής και μηνύματος
+ Εμφάνιση επιλογών για προγραμματιστές
+ Εμφάνιση πληροφοριών για
+ Εμφάνιση εσωτερικών σφαλμάτων
+ Εμφάνιση τελευταίων μηνυμάτων
+ Εμφάνιση κατάστασης μηνύματος
+ Εμφάνιση μόνο της επαφής
+ Εμφάνιση ποσοστού
+ Εμφάνιση προεπισκόπησης
+ Εμφάνιση κωδικού QR
+ Εμφάνιση αργών κλήσεων API
+ Απενεργοποίηση
+ Απενεργοποίηση;
+ SImpleX
+ SimpleX διεύθυνση
+ Διεύθυνση SimpleX
+ Η διεύθυνση SimpleX και οι σύνδεσμοι 1-χρήσης είναι ασφαλές να διαμοιράζονται μέσω οποιασδήποτε εφαρμογής ανταλλαγής μηνυμάτων.
+ Διεύθυνση SimpleX ή σύνδεσμος 1-χρήσης;
+ Το SimpleX δεν μπορεί να λειτουργήσει στο παρασκήνιο. Θα λαμβάνεις τις ειδοποιήσεις μόνο όταν η εφαρμογή είναι σε λειτουργία.
+ Σύνδεσμος καναλιού SimpleX
+ Η SimpleX Chat και η Flux σύναψαν συμφωνία για την ενσωμάτωση των διακομιστών που λειτουργεί η Flux, στην εφαρμογή.
+ Κλήσεις SimpleX Chat
+ Μηνύματα SimpleX Chat
+ Η ασφάλεια του SimpleX Chat ελέγχθηκε από την Trail of Bits.
+ Υπηρεσία SimpleX Chat
+ Διεύθυνση επικοινωνίας SimpleX
+ Σύνδεσμος ομάδας SimpleX
+ Σύνδεσμοι SimpleX
+ Σύνδεσμοι SimpleX
+ Οι σύνδεσμοι SimpleX απαγορεύονται.
+ Οι σύνδεσμοι SimpleX δεν επιτρέπονται
+ SimpleX Lock
+ SimpleX Lock
+ Λειτουργία SimpleX Lock
+ Το SimpleX Lock δεν είναι ενεργοποιημένο!
+ Το SimpleX Lock είναι ενεργοποιημένο
+ SimpleX Logo
+ simplexmq: v%s (%2s)
+ Πρόσκληση 1-χρήσης SimpleX
+ Πρωτόκολλα SimpleX που έχουν ελεγχθεί από την Trail of Bits.
+ Σύνδεσμος αναμεταδότη SimpleX
+ SimpleX Team
+ Απλοποιημένη ανώνυμη λειτουργία
+ %s δεν έχει επαληθευτεί
+ %s έχει επαληθευτεί
+ Μέγεθος
+ Παράλειψη πρόσκλησης μελών
+ Παραλειπόμενα μηνύματα
+ Παράλειψη αυτής της έκδοσης
+ Αργή λειτουργία
+ Μικρές ομάδες (μέγιστο 20 άτομα)
+ Διακομιστής SMP
+ Διακομιστές SMP
+ Διακομιστής μεσολάβησης SOCKS
+ ΔΙΑΚΟΜΙΣΤΗΣ ΜΕΣΟΛΑΒΗΣΗΣ SOCKS
+ Ρυθμίσεις διακομιστή μεσολάβησης SOCKS
+ Απαλό
+ Κάποιο/α αρχείο/α δεν εξήχθησαν
+ Κατά την εισαγωγή προέκυψαν ορισμένα μη κρίσιμα σφάλματα:
+ Ορισμένοι διακομιστές απέτυχαν στη δοκιμή:
+ Ήχος σε σίγαση
+ Spam
+ Spam
+ Ηχείο
+ Απενεργοποίηση ηχείου
+ Εεργοποίηση ηχείου
+ Τετράγωνο, κύκλος ή οτιδήποτε μεταξύ τους.
+ %s: %s
+ %s, %s και %d μέλη
+ %s, %s και %d άλλα μέλη συνδεδεμένα
+ %s, %s και %s συνδεδεμένα
+ %s δευτερόλεπτο/α
+ %s διακομιστές
+ Σταθερή
+ τυποποιημένη κρυπτογράφηση από άκρη-σε-άκρη
+ Αστέρι στο GitHub
+ Εκκίνηση συνομιλίας
+ Εκκίνηση συνομιλίας;
+ εκκινεί…
+ Εκκινεί από %s.
+ Εκκινεί από %s.\nΌλα τα δεδομένα παραμένουν ιδιωτικά στη συσκευή σου.
+ Εκκίνηση νέας συνομιλίας
+ Εκκινεί περιοδικά
+ Στατιστικά
+ Διακοπή
+ Διακοπή
+ Διακοπή συνομιλίας
+ Διακοπή συνομιλίας;
+ Διέκοψε τη συνομιλία για να εξάγεις, να εισάγεις ή να διαγράψεις τη βάση δεδομένων συνομιλιών. Δεν θα μπορείς να λαμβάνεις και να στέλνεις μηνύματα ενώ η συνομιλία έχει διακοπεί.
+ Διακοπή αρχείου
+ Διακοπή συνομιλίας
+ Διακοπή λήψης αρχείου;
+ Διακοπή αποστολής αρχείου;
+ Διακοπή διαμοιρασμού
+ Διακοπή διαμοιρασμού διεύθυνσης;
+ διαγράμμιση
+ Έντονο
+ Υποβολή
+ Εγγεγραμμένος
+ Σφάλματα εγγραφής
+ Η εγγραφή αγνοήθηκε
+ %s ανεβασμένα
+ Υποστήριξη bluetooth και άλλων βελτιώσεων.
+ ΥΠΟΣΤΗΡΙΞΗ SIMPLEX CHAT
+ Ενάλλαξε
+ Εναλλαγή ήχου και βίντεο κατά τη διάρκεια της κλήσης.
+ Αλλαγή προφίλ συνομιλίας για προσκλήσεις 1-χρήσης.
+ Σύστημα
+ Σύστημα
+ Σύστημα
+ Σύστημα
+ Αυθεντικοποίηση συστήματος
+ Λειτουργία συστήματος
+ Ουρά
+ Πάτα το κουμπί
+ Επαλήθευση κωδικού στο κινητό
+ Επαλήθευση κωδικού με υπολογιστή
+ Επαλήθευση σύνδεσης
+ Επαλήθευση συνδέσεων
+ Επαλήθευση ασφάλειας σύνδεσης
+ Επαλήθευση φράσης πρόσβασης της βάσης δεδομένων
+ Επαλήθευση φράσης πρόσβασης
+ Επαλήθευση κωδικού ασφαλείας
+ μέσω %1$s
+ Μέσω περιηγητή
+ μέσω του συνδέσμου διεύθυνσης επαφής
+ μέσω συνδέσμου ομάδας
+ μέσω συνδέσμου 1-χρήσης
+ μέσω αναμεταδότη
+ Μέσω ασφαλούς κβαντο-ανθεκτικού πρωτοκόλλου
+ βίντεο
+ Βίντεο
+ Βίντεο
+ βιντεοκλήση
+ Βιντεοκλήση
+ βιντεοκλήση (χωρίς κρυπτογράφηση e2e)
+ Βίντεο απενεργοποιημένο
+ Βίντεο ενεργοποιημένο
+ Βίντεο και αρχεία εώς 1gb
+ Βίντεο απεστάλη
+ Το βίντεο θα ληφθεί όταν η επαφή σου ολοκληρώσει τη μεταφόρτωσή του.
+ Το βίντεο θα ληφθεί όταν η επαφή σου είναι συνδεδεμένη, παρακαλώ περίμενε ή έλεγξε αργότερα!
+ Δες τους όρους
+ Προβολή κωδικού ασφαλείας
+ Προβολή ενημερωμένων συνθηκών
+ Ορατό ιστορικό
+ Φωνητικό μήνυμα
+ Φωνητικό μήνυμα…
+ Φωνητικό μήνυμα (%1$s)
+ Φωνητικά μηνύματα
+ Φωνητικά μηνύματα
+ Τα φωνητικά μηνύματα απαγορεύονται.
+ Τα φωνητικά μηνύματα απαγορεύονται σε αυτήν τη συνομιλία.
+ Τα φωνητικά μηνύματα δεν επιτρέπονται
+ Τα φωνητικά μηνύματα απαγορεύονται!
+ - φωνητικά μηνύματα εώς 5 λεπτά.\n- προσαρμοσμένος χρόνος εξαφάνισης.\n- ιστορικό επεξεργασίας.
+ αναμονή για απάντηση…
+ αναμονή για επιβεβαίωση…
+ Αναμονή για τον υπολογιστή…
+ Αναμονή για το αρχείο
+ Αναμονή για την εικόνα
+ Αναμονή για την εικόνα
+ Αναμονή σύνδεσης κινητού:
+ Αναμονή για το βίντεο
+ Αναμονή για το βίντεο
+ Χρωματική έμφαση ταπετσαρίας
+ Φόντο ταπετσαρίας
+ θέλει να συνδεθεί μαζί σου!
+ Προειδοποίηση: η έναρξη συνομιλίας σε πολλαπλές συσκευές δεν υποστηρίζεται και θα προκαλέσει σφάλματα στην παράδοση των μηνυμάτων.
+ Προειδοποίηση: ενδέχεται να χάσεις ορισμένα δεδομένα!
+ Διακομιστές WebRTC ICE
+ Ιστοσελίδα
+ Δεν αποθηκεύουμε καμία από τις επαφές ή τα μηνύματά σου (αφού παραδοθούν) στους διακομιστές.
+ εβδομάδες
+ Καλωσόρισες!
+ Καλωσόρισες %1$s!
+ Μήνυμα καλωσορίσματος
+ Μήνυμα καλωσορίσματος
+ Μήνυμα καλωσορίσματος
+ Το μήνυμα καλωσορίσματος είναι πολύ μεγάλο
+ Καλωσόρισε τις επαφές σου 👋
+ Τι νέο υπάρχει
+ Όταν η εφαρμογή είναι σε λειτουργία
+ Όταν είναι διαθέσιμο
+ Κατά τη σύνδεση κλήσεων ήχου και βίντεο.
+ Όταν η IP είναι κρυφή
+ Όταν είναι ενεργοποιημένοι περισσότεροι από ένας χειριστές, κανένας από αυτούς δεν διαθέτει μεταδεδομένα για να μάθει ποιος επικοινωνεί με ποιον.
+ Όταν κάποιος ζητήσει να συνδεθεί, μπορείς να αποδεχτείς ή να απορρίψεις το αίτημα.
+ Όταν μοιράζεσε ένα ανώνυμο προφίλ με κάποιον, αυτό το προφίλ θα χρησιμοποιείται για τις ομάδες στις οποίες σε προσκαλούν.
+ WiFi
+ Θα ενεργοποιηθεί στις άμεσες συνομιλίες!
+ Ενσύρματο ethernet
+ Με κρυπτογραφημένα αρχεία και μέσα.
+ Με προαιρετικό μήνυμα καλωσορίσματος.
+ Χωρίς Tor ή VPN, η διεύθυνση IP σου θα είναι ορατή στους διακομιστές αρχείων.
+ Χωρίς Tor ή VPN, η διεύθυνση IP σου θα είναι ορατή σε αυτούς τους XFTP αναμεταδότες:\n%1$s.
+ Με μειωμένη χρήση της μπαταρίας.
+ Με μειωμένη χρήση της μπαταρίας.
+ Λανθασμένη φράση πρόσβασης της βάσης δεδομένων
+ Λανθασμεο κλειδί ή άγνωστη σύνδεση - πιθανότατα αυτή η σύνδεση έχει διαγραφεί.
+ Λανθασμένο κλειδί ή άγνωστη διεύθυνση τμήματος αρχείου - πιθανότατα το αρχείο έχει διαγραφεί.
+ Λανθασμένη φράση πρόσβασης!
+ Διακομιστής XFTP
+ Διακομιστές XFTP
+ ναι
+ Ναι
+ Ναι
+ εσύ
+ ΕΣΥ
+ εσύ: %1$s
+ Αποδέχθηκες τη σύνδεση
+ αποδέχθηκες αυτό το μέλος
+ Επιτρέπεις
+ Έχεις ήδη ένα προφίλ συνομιλίας με το ίδιο όνομα εμφάνισης. Παρακαλώ επέλεξε ένα άλλο όνομα.
+ Είσαι ήδη συνδεδεμένος στο %1$s.
+ Ήδη συνδέεσαι μέσω αυτού του μοναδικού συνδέσμου!
+ Έχεις ήδη ενταχθεί στην ομάδα μέσω αυτού του συνδέσμου.
+ Είσαι προσκεκλημένος στην ομάδα
+ Είσαι προσκεκλημένος στην ομάδα
+ Είσαι προσκεκλημένος στην ομάδα. Αποδέξου την πρόσκληση για να συνδεθείς με τα μέλη της ομάδας.
+ Δεν είσαι συνδεδεμένος στον διακομιστή που χρησιμοποιείται για τη λήψη μηνυμάτων από αυτή τη σύνδεση (δεν υπάρχει συνδρομή).
+ Δεν είσαι συνδεδεμένος σε αυτούς τους διακομιστές. Για την παράδοση μηνυμάτων σε αυτούς, χρησιμοποιείται ιδιωτική δρομολόγηση.
+ είσαι παρατηρητής
+ είσαι παρατηρητής
+ μπλόκαρες %s
+ Μπορείς να το αλλάξεις στις ρυθμίσεις Εμφάνισης.
+ Μπορείς να διαμορφώσεις τους χειριστές στις ρυθμίσεις Δικτύου & διακομιστών.
+ Μπορείς να διαμορφώσεις τους διακομιστές μέσω των ρυθμίσεων.
+ Μπορείς να αντιγράψεις και να μειώσεις το μέγεθος του μηνύματος για να το στείλεις.
+ Μπορείς να το δημιουργήσεις αργότερα
+ Μπορείς να το ενεργοποιήσεις αργότερα μέσω των Ρυθμίσεων.
+ Μπορείς να τις ενεργοποιήσεις αργότερα μέσω των ρυθμίσεων απορρήτου και ασφάλειας της εφαρμογής.
+ Μπορείς να δοκιμάσεις ξανά.
+ Μπορείς να δοκιμάσεις ξανά.
+ Μπορείς να αποκρύψεις ή να σιγάσεις ένα προφίλ χρήστη - κράτησέ το πατημένο για να εμφανιστεί το μενού.
+ Μπορείς να το κάνεις ορατό στις επαφές σου στο SimpleX μέσω των Ρυθμίσεων.
+ Μπορείς να αναφέρεις εώς και %1$s μέλη ανά μήνυμα!
+ Μπορείς να στείλεις μηνύματα στην επαφή %1$s από τις αρχειοθετημένες επαφές.
+ Μπορείς να ορίσεις το όνομα της σύνδεσης για να θυμάσε με ποιον μοιράστηκες το σύνδεσμο.
+ Μπορείς να μοιραστείς ένα σύνδεσμο ή έναν κωδικό QR - οποιοσδήποτε θα μπορεί να συμμετάσχει στην ομάδα. Δεν θα χάσεις μέλη της ομάδας αν τον διαγράψεις αργότερα.
+ Μπορείς να μοιραστείς αυτήν τη διεύθυνση με τις επαφές σου για να τους επιτρέψεις να συνδεθούν με την επαφή %s.
+ Μπορείς να διαμοιραστείς τη διεύθυνσή σου ως σύνδεσμο ή κωδικό QR - οποιοσδήποτε θα μπορεί να συνδεθεί μαζί σου.
+ Μπορείς να ξεκινήσεις τη συνομιλία μέσω της εφαρμογής Ρυθμίσεις / Βάση δεδομένων ή επανεκκινώντας την εφαρμογή.
+ Μπορείς ακόμα να δεις τη συνομιλία με την επαφή %1$s, στη λίστα των συνομιλιών.
+ Δεν μπορείς να στείλεις μηνύματα!
+ Μπορείς να ενεργοποιήσεις το SimpleX Lock μέσω των Ρυθμίσεων.
+ Μπορείς να χρησιμοποιήσεις σύνταξη markdown για να μορφοποιήσεις τα μηνύματα:
+ Μπορείς να δεις ξανά το σύνδεσμο πρόσκλησης στις λεπτομέρειες σύνδεσης.
+ Μπορείς να δείς τις αναφορές σου στη Συνομιλία με τους διαχειριστές.
+ άλλαξες διεύθυνση
+ άλλαξες διεύθυνση για %s
+ άλλαξες ρόλο για τον εαυτό σου σε %s
+ άλλαξες το ρόλο του μέλους %s σε %s
+ Έχεις τον έλεγχο της συνομιλίας σου!
+ Δεν ήταν δυνατή η επαλήθευση. Παρακαλώ, δοκίμασε ξανά.
+ Εσύ αποφασίζεις ποιος μπορεί να συνδεθεί.
+ Έχεις ήδη ζητήσει σύνδεση μέσω αυτής της διεύθυνσης!
+ Δεν έχεις συνομιλίες
+ Πρέπει να εισάγεις τη φράση πρόσβασης κάθε φορά που ξεκινά η εφαρμογή - δεν αποθηκεύεται στη συσκευή.
+ Προσκάλεσες μία επαφή
+ Εντάχθηκες σε αυτήν την ομάδα
+ Έχεις ενταχθεί σε αυτή την ομάδα. Σύνδεση με το μέλος που σε προσκάλεσε.
+ αποχώρησες
+ αποχώρησες
+ Μπορείς να μεταφέρεις την εξαγώμενη βάση δεδομένων.
+ Μπορείς να αποθηκεύσεις το εξαγώμενο αρχείο.
+ Πρέπει να χρησιμοποιήσεις την πιο πρόσφατη έκδοση της βάσης δεδομένων συνομιλιών σου σε ΜΟΝΟ μία συσκευή, διαφορετικά ενδέχεται να σταματήσεις να λαμβάνεις μηνύματα από ορισμένες επαφές.
+ Πρέπει να επιτρέψεις στην επαφή σου να σε καλέσει για να μπορείς να την καλέσεις πίσω.
+ Για να μπορείς να στέλνεις φωνητικά μηνύματα, πρέπει να επιτρέψεις στην επαφή σου να στέλνει φωνητικά μηνύματα.
+ Η επαγγελματική σου επαφή
+ Η κλήσεις σου
+ Η βάση δεδομένων συνομιλιών σου
+ Η βάση δεδομένων συνομιλιών σου δεν είναι κρυπτογραφημένη - όρισε μία φράση πρόσβασης για να την προστατεύσεις.
+ Τα προφίλ συνομιλιών σου
+ Το προφίλ συνομιλίας σου θα σταλεί στα μέλη της συνομιλίας.
+ Το προφίλ συνομιλίας σου θα σταλεί στα μέλη της ομάδας.
+ Η σύνδεσή σου μεταφέρθηκε στο προφίλ %s, αλλά προέκυψε σφάλμα κατά την εναλλαγή του.
+ Η επαφή σου
+ Η επαφή σου πρέπει να είναι συνδεδεμένη στο διαδίκτυο για να ολοκληρωθεί η σύνδεση.\nΜπορείς να ακυρώσεις αυτήν τη σύνδεση και να καταργήσεις την επαφή (και να δοκιμάσεις αργότερα με έναν νέο σύνδεσμο).
+ Οι επαφές σου
+ Οι επαφές σου μπορούν να επιτρέψουν την πλήρη διαγραφή μηνυμάτων.
+ Τα διαπιστευτήριά σου ενδέχεται να αποσταλούν χωρίς κρυπτογράφηση.
+ Η τρέχουσα βάση δεδομένων συνομιλιών σου θα ΔΙΑΓΡΑΦΕΙ και θα ΑΝΤΙΚΑΤΑΣΤΑΘΕΙ με την εισαγώμενη.\nΑυτή η ενέργεια δεν μπορεί να αναιρεθεί - το προφίλ, οι επαφές, τα μηνύματα και τα αρχεία σου θα χαθούν οριστικά.
+ Προσπαθείς να προσκαλέσεις μία επαφή με την οποία έχεις μοιραστεί ένα ανώνυμο προφίλ στην ομάδα στην οποία χρησιμοποιείς το κύριο προφίλ σου.
+ Χρησιμοποιείς ένα ανώνυμο προφίλ για αυτήν την ομάδα - για να αποφύγεις την κοινή χρήση του κύριου προφίλ σου, δεν επιτρέπεται η πρόσκληση επαφών.
+ Η ομάδα σου
+ Το προφίλ σου
+ Το προφίλ σου αποθηκεύεται στη συσκευή σου και κοινοποιείται μόνο στις επαφές σου. Οι διακομιστές της SimpleX δεν μπορούν να δουν το προφίλ σου.
+ Οι διακομιστές σου
+ διαμοιράστηκες ένα σύνδεσμο 1-χρήσης
+ διαμοιράστηκες ένα σύνδεσμο 1-χρήσης ανώνυμα
+ ξεμπλόκαρες %s
+ Θα συνδεθείς στην ομάδα όταν η συσκευή του διαχειριστή της ομάδας είναι συνδεδεμένη στο διαδίκτυο. Παρακαλώ περίμενε ή έλεγξε αργότερα!
+ Θα συνδεθείς όταν γίνει αποδεκτό το αίτημά σου για σύνδεση. Παρακαλώ περίμενε ή έλεγξε αργότερα!
+ Θα σου ζητηθεί να πραγματοποιήσεις έλεγχο ταυτότητας όταν ξεκινήσεις ή συνεχίσεις την εφαρμογή μετά από 30 δευτερόλεπτα στο παρασκήνιο.
+ Θα συνεχίσεις να λαμβάνεις κλήσεις και ειδοποιήσεις από τα προφίλ που έχεις σε σίγαση όταν αυτά θα είναι ενεργά.
+ Δεν θα λαμβάνεις πλέον μηνύματα από αυτήν τη συνομιλία. Το ιστορικό συνομιλιών θα διατηρηθεί.
+ Δεν θα λαμβάνεις πλέον μηνύματα από αυτήν την ομάδα. Το ιστορικό συνομιλιών θα διατηρηθεί.
+ Δεν θα χάσεις τις επαφές σου αν διαγράψεις αργότερα τη διεύθυνσή σου.
+ Μεγέθυνση
+ Όλα τα μηνύματα
+ Αρχεία
+ ΦΙλτράρισμα
+ Εικόνες
+ Σύνδεσμοι
+ Αναζήτηση αρχείων
+ Αναζήτηση εικόνων
+ Αναζήτηση συνδέσμων
+ Αναζήτηση βίντεο
+ Αναζήτηση φωνητικών μηνυμάτων
+ Βίντεο
+ Φωνητικά μηνύματα
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml
index b5e756aaad..c233d8eabc 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml
@@ -1732,7 +1732,7 @@
Descargar
Reenviar
Reenviado
- Mensaje reenviado…
+ Reenviando mensaje…
Los destinatarios no ven de quién procede este mensaje.
Bluetooth
Concurrencia en la recepción
@@ -1906,7 +1906,7 @@
Las estadísticas de los servidores serán restablecidas. ¡No puede deshacerse!
Descargado
Servidor SMP
- Aún no hay conexión directa, el mensaje es reenviado por el administrador.
+ Aún no hay conexión directa, los mensajes son reenviados por el administrador.
Otros servidores SMP
Otros servidores XFTP
Escanear / Pegar enlace
@@ -2391,7 +2391,7 @@
Error al aceptar el miembro
¿Guardar configuración?
Por favor, espera a que tu solicitud sea revisada por los moderadores del grupo.
- has aceptado al miembro
+ has admitido al miembro
pendiente de revisión
por revisar
Chat con administradores
@@ -2419,7 +2419,7 @@
¡No puedes enviar mensajes!
Puedes ver tus informes en Chat con administradores
has salido
- te ha aceptado
+ te ha admitido
Un miembro nuevo desea unirse al grupo.
todos
Chat con miembros
@@ -2537,4 +2537,21 @@
La huella en la dirección del servidor de reenvío no coincide con el certificado: %1$s.
Sin suscripciones
No estás conectado al servidor usado para recibir mensajes de esta conexión (no suscrito).
+ Eliminar mensajes del miembro
+ ¿Eliminar mensajes del miembro?
+ Eliminar mensajes
+ Los mensajes del miembro serán eliminados. ¡No puede deshacerse!
+ Eliminar miembro y sus mensajes
+ Todos los mensajes
+ Archivos
+ Filtro
+ Imágenes
+ Enlaces
+ Buscar archivos
+ Buscar imágenes
+ Buscar enlaces
+ Buscar vídeos
+ Buscar mensajes de voz
+ Vídeos
+ Mensajes de voz
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml
index eba31ba788..5483becb91 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml
@@ -2532,4 +2532,5 @@
اثر انگشت در نشانی سرور مقصد با گواهی مطابقت ندارد: %1$s.
اثر انگشت در نشانی سرور انتقال با گواهی مطابقت ندارد: %1$s.
اثر انگشت در نشانی سرور با گواهی مطابقت ندارد: %1$s.
+ پاک کردن پیام کاربر
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml
index 12b578edbf..38f8a81d3a 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml
@@ -21,7 +21,7 @@
A SimpleXről
Kiemelőszín
fogadott hívás
- Hozzáférés a kiszolgálókhoz SOCKS proxyn a következő porton keresztül: %d? A proxyt el kell indítani, mielőtt engedélyezné ezt az opciót.
+ Hozzáférés a kiszolgálókhoz SOCKS proxyn a következő porton keresztül: %d? A proxyt el kell indítani, mielőtt engedélyezné ezt a beállítást.
Elfogadás
Elfogadás
gombra fent, majd:
@@ -39,7 +39,7 @@
Előre beállított kiszolgálók hozzáadása
A hívások kezdeményezése le van tiltva.
Az összes partneréhez és csoporttaghoz külön TCP-kapcsolat (és SOCKS-hitelesítési adat) lesz használva.\nMegjegyzés: ha sok kapcsolata van, akkor az akkumulátor-használat és az adatforgalom jelentősen megnövekedhet, és néhány kapcsolódási kísérlet sikertelen lehet.]]>
- hivatkozás előnézetének visszavonása
+ hivatkozáselőnézet visszavonása
Az összes csevegési profiljához az alkalmazásban külön TCP-kapcsolat (és SOCKS-hitelesítési adat) lesz használva.]]>
Mindkét fél küldhet eltűnő üzeneteket.
Az Android Keystore-t a jelmondat biztonságos tárolására használják – lehetővé teszi az értesítési szolgáltatás működését.
@@ -48,7 +48,7 @@
Megjegyzés: az üzenet- és fájltovábbító kiszolgálók SOCKS proxyn keresztül kapcsolódnak. A hívások és a hivatkozások előnézetének küldése közvetlen kapcsolatot használ.]]>
Alkalmazásadatok biztonsági mentése
Az adatbázis előkészítése sikertelen
- A partnereivel kapcsolatban marad. A profilfrissítés el lesz küldve a partnerei számára.
+ Az összes partnerével továbbra is kapcsolatban marad. A profilfrissítés el lesz küldve a partnerei számára.
A csevegési profillal (alapértelmezett), vagy a kapcsolattal (BÉTA).
Egy új, véletlenszerű profil lesz megosztva.
A hangüzenetek küldése csak abban az esetben van engedélyezve, ha a partnere is engedélyezi.
@@ -59,7 +59,7 @@
Hang- és videóhívások
Az alkalmazás titkosítja a helyi fájlokat (a videók kivételével).
Hívás fogadása
- Az eltűnő üzenetek küldésének engedélyezése a partnerei számára.
+ Az eltűnő üzenetek küldése engedélyezve van a partnerei számára.
Kapcsolódás folyamatban!
Nem lehet fogadni a fájlt
Hitelesítés elérhetetlen
@@ -70,7 +70,7 @@
Mindkét fél véglegesen törölheti az elküldött üzeneteket. (24 óra)
Továbbfejlesztett csoportok
Az összes üzenet törölve lesz – ez a művelet nem vonható vissza! Az üzenetek CSAK az Ön számára törlődnek.
- Hívás befejeződött
+ A hívás véget ért
HÍVÁSOK
és további %d esemény
Cím
@@ -86,7 +86,7 @@
Vissza
Kikapcsolható a beállításokban – az értesítések továbbra is meg lesznek jelenítve amíg az alkalmazás fut.]]>
Az adminisztrátorok hivatkozásokat hozhatnak létre a csoportokhoz való csatlakozáshoz.
- Hívások a zárolási képernyőn:
+ Hívások a zárolási képernyőn
titkosítás elfogadása…
Nem lehet meghívni a partnert!
hibás az üzenet azonosítója
@@ -97,7 +97,7 @@
Hozzáadás egy másik eszközhöz
A reakciók hozzáadása az üzenetekhez engedélyezve van.
Fájlelőnézet visszavonása
- Az összes csoporttag kapcsolatban marad.
+ Az összes csoporttag továbbra is kapcsolatban marad.
Több akkumulátort használ! Az alkalmazás mindig fut a háttérben – az értesítések azonnal megjelennek.]]>
Letiltás
adminisztrátor
@@ -114,11 +114,11 @@
Az alkalmazásjelkód helyettesítve lesz egy önmegsemmisítő jelkóddal.
Arab, bolgár, finn, héber, thai és ukrán – köszönet a felhasználóknak és a Weblate-nek.
Engedélyezi a hangüzeneteket?
- Mindig használjon továbbítókiszolgálót
+ Mindig legyen használva továbbítókiszolgáló
mindig
- A hívás már befejeződött!
+ A hívás már véget ért!
Engedélyezés
- Az összes partnerével kapcsolatban marad.
+ Az összes partnerével továbbra is kapcsolatban marad.
Élő csevegési üzenet visszavonása
Az üzenetek végleges törlése csak abban az esetben van engedélyezve, ha a partnere is engedélyezi. (24 óra)
Hang- és videóhívások
@@ -130,7 +130,7 @@
Megjelenés
Az akkumulátor-optimalizálás aktív, ez kikapcsolja a háttérszolgáltatást és az új üzenetek időszakos lekérdezését. Ezt a beállításokban újraengedélyezheti.
Letiltja a tagot?
- %1$s hívása befejeződött
+ %1$s hívása véget ért
Jó akkumulátoridő. Az alkalmazás 10 percenként ellenőrzi az új üzeneteket. Előfordulhat, hogy hívásokról, vagy a sürgős üzenetekről marad le.]]>
szerző
Az elküldött üzenetek végleges törlése engedélyezve van a partnerei számára. (24 óra)
@@ -165,7 +165,7 @@
A hívások kezdeményezése csak abban az esetben van engedélyezve, ha a partnere is engedélyezi.
Kiszolgáló hozzáadása
Hang bekapcsolva
- hanghívás (nem e2e titkosított)
+ hanghívás (végpontok között NEM titkosított)
letiltva
Módosítja az adatbázis jelmondatát?
kapcsolódva
@@ -195,11 +195,11 @@
Kapcsolódás
partneri kapcsolatot kért
kapcsolat %1$d
- a partner e2e titkosítással rendelkezik
+ a partner végpontok közötti titkosítással rendelkezik
Csoport létrehozása véletlenszerű profillal.
A partner és az összes üzenet törölve lesz – ez a művelet nem vonható vissza!
A partnerei törlésre jelölhetnek üzeneteket; Ön majd meg tudja nézni azokat.
- Kapcsolódik az egyszer használható meghívóval?
+ Kapcsolódik az egyszer használható meghívón keresztül?
Kapcsolódás egy hivatkozáson vagy QR-kódon keresztül
Kapcsolódási hiba (AUTH)
Csak név
@@ -214,7 +214,7 @@
Kapcsolódik saját magához?
Vágólapra másolva
Kapcsolódási kérés elküldve!
- Kapcsolódás a számítógéphez
+ Társítás számítógéppel
Kapcsolat
Helyesbíti a nevet a következőre: %s?
Időtúllépés kapcsolódáskor
@@ -224,7 +224,7 @@
Kapcsolat
Kapcsolat megszakítva
kapcsolat létrehozva
- a partner nem rendelkezik e2e titkosítással
+ a partner nem rendelkezik végpontok közötti titkosítással
Partner engedélyezi
Rejtett név:
Társítás számítógéppel
@@ -266,25 +266,25 @@
kapcsolódás…
Csevegési profil törlése
egyéni
- kapcsolódási hívás…
+ hívás kapcsolása…
Téma személyre szabása
- Jelenleg támogatott legnagyobb fájl méret: %1$s.
+ Jelenleg támogatott legnagyobb fájlméret: %1$s.
Fájl törlése
Hamarosan!
cím módosítása %s számára…
Csevegési adatbázis importálva
Üzenetek törlése
- Kiürítés
+ Ürítés
Bezárás gomb
A csevegés megállt
(jelenlegi)
Témák személyre szabása és megosztása.
Törli a csevegési profilt?
Titkos csoport létrehozása
- Kapcsolódva a számítógéphez
+ Társítva a számítógéppel
ICE-kiszolgálók beállítása
Csoport törlése
- Hitelesítés törlése
+ Ellenőrzés törlése
készítő
Megerősítés
Csak nálam
@@ -314,7 +314,7 @@
Egyéni időköz
Kapcsolódás inkognitóban
CSEVEGÉSEK
- Új profil létrehozása a számítógép alkalmazásban. 💻
+ Új profil létrehozása a számítógépes alkalmazásban. 💻
kapcsolódás (bejelentve)
kapcsolódás…
Csevegési adatbázis törölve
@@ -333,7 +333,7 @@
Titkos csoport létrehozása
Elvetés
Törli a partnert?
- Kiürítés
+ Ürítés
Cím létrehozása, hogy az emberek kapcsolatba léphessenek Önnel.
Biztonsági kódok összehasonlítása a partnerekével.
Fájl-összehasonlítás
@@ -341,9 +341,9 @@
Törli az üzenetet?
Törli a függőben lévő kapcsolatot?
Adatbázis titkosítva!
- Kiüríti a csevegést?
+ Üríti a csevegés üzeneteit?
Adatbázis visszafejlesztése
- Üzenetek kiürítése
+ Csevegés üzeneteinek ürítése
Az adatbázis titkosítási jelmondata frissítve lesz.
Kapcsolódás automatikusan
Adatbázishiba
@@ -390,15 +390,15 @@
%2$s %1$d üzenetet moderált
Eltűnő üzenet
Ne hozzon létre címet
- Ne mutasd újra
+ Ne jelenjen meg újra
SimpleX-zár kikapcsolása
- e2e titkosított
+ végpontok között titkosított
ESZKÖZ
- e2e titkosított videóhívás
+ végpontok között titkosított videóhívás
közvetlen
Számítógép
%d perc
- %d partner kijelölve
+ %d partner kiválasztva
Engedélyezés
%dhónap
A közvetlen üzenetek küldése a tagok között le van tiltva ebben a csoportban.
@@ -419,7 +419,7 @@
Törlés, és a partner értesítése
letiltva
%d mp
- Az összes fájl törlése
+ Összes fájl törlése
Az adatbázis titkosítva lesz.
Adatbázis-jelmondat és -exportálás
Az adatbázis titkosítva lesz, a jelmondat pedig a Keystore-ban lesz tárolva.
@@ -435,7 +435,7 @@
%d csoportesemény
%d hónap
Csoportprofil szerkesztése
- e2e titkosított hanghívás
+ végpontok között titkosított hanghívás
%d mp
Decentralizált
Dekódolási hiba
@@ -443,12 +443,12 @@
Értesítések letiltása
Eszközök
Látható a helyi hálózaton
- Ne engedélyezze
+ Nem engedélyezem
Az eltűnő üzenetek küldése le van tiltva ebben a csevegésben.
alapértelmezett (%s)
duplikált üzenet
Leválasztja a számítógépet?
- A számítógép-alkalmazás verziója (%s) nem kompatibilis ezzel az alkalmazással.
+ A számítógépes alkalmazás verziója (%s) nem kompatibilis ezzel az alkalmazással.
Kézbesítés
%d fájl, %s összméretben
A csevegés megnyitásához adja meg az adatbázis jelmondatát.
@@ -495,7 +495,7 @@
A csoportprofil a tagok eszközein tárolódik, nem a kiszolgálókon.
Adja meg a jelmondatot…
Hiba történt a felhasználói adatvédelem frissítésekor
- Titkosít
+ Titkosítás
Csoport nem található!
Hiba történt az SMP-kiszolgálók mentésekor
Visszafejlesztés és a csevegés megnyitása
@@ -566,7 +566,7 @@
Hiba történt az XFTP-kiszolgálók mentésekor
A tagok küldhetnek egymásnak közvetlen üzeneteket.
Hiba történt a tag eltávolításakor
- befejeződött
+ hívás vége
A csoport üdvözlőüzenete
Adja meg a csoport nevét:
Hiba történt a meghívó elküldésekor
@@ -631,7 +631,7 @@
Téves jelkód
Azonnali
Inkognitócsoportok
- Hogyan
+ Útmutató
Összecsukás
Kép
Továbbfejlesztett adatvédelem és biztonság
@@ -695,7 +695,7 @@
moderált
A tag el lesz távolítva a csoportból – ez a művelet nem vonható vissza!
Győződjön meg arról, hogy a megadott XFTP-kiszolgálók címei megfelelő formátumúak, soronként elkülönítettek, és nincsenek duplikálva.
- Nincs partner kijelölve
+ Nincs partner kiválasztva
Nincsenek fogadott vagy küldött fájlok
Megnyitás hordozható eszköz-alkalmazásban, majd koppintson a Kapcsolódás gombra az alkalmazásban.]]>
Markdown az üzenetekben
@@ -711,13 +711,13 @@
Helyi név
Hálózat és kiszolgálók
Értesítésekben megjelenő információk
- Társítsa össze a hordozható eszköz- és számítógépes alkalmazásokat! 🔗
+ Társítsa össze a hordozható eszköz- és a számítógépes alkalmazásokat! 🔗
közvetett (%1$s)
Hamarosan további fejlesztések érkeznek!
A reakciók hozzáadása az üzenetekhez le van tiltva ebben a csevegésben.
Helytelen biztonsági kód!
Ez akkor fordulhat elő, ha Ön vagy a partnere egy régi adatbázis biztonsági mentését használta.
- Új számítógép-alkalmazás!
+ Új számítógépes alkalmazás!
Most már az adminisztrátorok is:\n- törölhetik a tagok üzeneteit.\n- letilthatnak tagokat (megfigyelő szerepkör)
meghívta őt: %1$s
A reakciók hozzáadása az üzenetekhez le van tiltva.
@@ -753,14 +753,14 @@
Az üzenetek végleges törlése le van tiltva.
%s nevű hordozható eszköz le lett választva]]>
hónap
- Üzenetvázlat
+ Piszkozatok
Egy üzenet eltüntetése
Végleges üzenettörlés
Egyszerre csak 10 videó küldhető el
Csak Ön adhat hozzá reakciókat az üzenetekhez.
elhagyta a csoportot
Az üzenetek végleges törlése le van tiltva ebben a csevegésben.
- Max 40 másodperc, azonnal fogadható.
+ Legfeljebb 40 másodperc, azonnal megérkezik.
inkognitó a kapcsolattartási címhivatkozáson keresztül
Onion kiszolgálók szükségesek a kapcsolódáshoz.\nMegjegyzés: .onion cím nélkül nem fog tudni kapcsolódni a kiszolgálókhoz.
Olasz kezelőfelület
@@ -777,7 +777,7 @@
Csak a csoport tulajdonosai engedélyezhetik a fájlok és a médiatartalmak küldését.
Fájl betöltése…
Nincs hozzáadandó partner
- Üzenetvázlat
+ Piszkozatok
függőben lévő kapcsolat
Egyszer használható meghívó
Értesítések
@@ -818,7 +818,7 @@
Menük és figyelmeztetések
Tagok meghívása
Csatlakozás mint: %s
- Nincs csevegés kijelölve
+ Nincs csevegés kiválasztva
Csak helyi profiladatok
inkognitó egy egyszer használható meghívón keresztül
Moderálva: %s
@@ -827,7 +827,7 @@
Beszélgessünk a SimpleX Chatben
Moderálva
Élő üzenetek
- Hitelesítés
+ Megjelölés ellenőrzöttként
Üzenetkézbesítési jelentések!
hivatkozás előnézeti képe
Elhagyja a csoportot?
@@ -838,7 +838,7 @@
Új megjelenítendő név:
Új jelmondat…
nem fogadott hívás
- Átköltöztetés: %s
+ Átköltöztetések: %s
Válaszul erre
Név és üzenet
Az értesítések csak az alkalmazás bezárásáig érkeznek!
@@ -851,7 +851,7 @@
dőlt
Érvénytelen a fájl elérési útvonala
Csatlakozik a csoporthoz?
- nincs e2e titkosítás
+ nincs végpontok közötti titkosítás
Új adatbázis-archívum
Élő üzenet!
Meghívás a csoportba
@@ -872,7 +872,7 @@
Időszakos
fogadott, tiltott
Megismétli a kapcsolódási kérést?
- Véglegesen csak Ön törölhet üzeneteket (partnere csak törlésre jelölheti meg őket ). (24 óra)
+ Csak Ön törölheti véglegesen az üzeneteket (partnere csak törlésre jelölheti meg azokat ). (24 óra)
Szerepkör
SimpleX kapcsolattartási cím
Megállítás
@@ -895,17 +895,17 @@
Jelentse a fejlesztőknek.
Ön dönti el, hogy kivel beszélget.
Az eltűnő üzenetek küldése le van tiltva.
- Csak Ön tud hangüzeneteket küldeni.
+ Csak Ön küldhet hangüzeneteket.
Frissítés
Videó elküldve
- Az adatbázis jelmondatának módosítása
+ Adatbázis jelmondatának módosítása
Alkalmazásbeállítások megnyitása
A jelkód nem módosult!
Frissítés
- Kijelölés
- Csak Ön tud hívásokat indítani.
+ Kiválasztás
+ Csak Ön kezdeményezhet hívásokat.
Biztonságos várólista
- Értékelje az alkalmazást
+ Alkalmazás értékelése
Egyszer használható meghívó megosztása
Hiba történt az adatbázis visszaállításakor
%s és %s
@@ -918,7 +918,7 @@
Fogadott üzenet
Üdvözlőüzenet
%s, %s és további %d tag kapcsolódott
- Csak a partnere tud hívást indítani.
+ Csak a partnere kezdeményezhet hívásokat.
TÉMÁK
Túl sok videó!
Üdvözöljük!
@@ -937,10 +937,10 @@
Hangszóró bekapcsolva
Importált csevegési adatbázis használatához indítsa újra az alkalmazást.
jogosulatlan küldés
- Csak a partnere tud hangüzeneteket küldeni.
+ Csak a partnere küldhet hangüzeneteket.
Beállítások
A kapcsolódáshoz a partnere beolvashatja a QR-kódot, vagy használhatja az alkalmazásban található hivatkozást.
- visszaigazolás fogadása…
+ visszaigazolás érkezett…
Biztonsági kód beolvasása a partnere alkalmazásából.
Lépjen kapcsolatba a csoport adminisztrátorával.
Videó bekapcsolva
@@ -952,7 +952,7 @@
Keresés
Újraegyezteti a titkosítást?
Az önmegsemmisítő jelkód engedélyezve!
- Biztonsági kiértékelés
+ Biztonsági felmérés
Cím
Üzenet elküldése
Adatbázismentés visszaállítása
@@ -1027,13 +1027,13 @@
SIMPLEX CHAT TÁMOGATÁSA
SimpleX Chat szolgáltatás
Ön megfigyelő
- %s hitelesítve
+ %s ellenőrizve
Jelszó a megjelenítéshez
Adatvédelem és biztonság
Eltávolítás
A jelkód beállítva!
Elküldött üzenet
- Partnerek kijelölése
+ Partnerek kiválasztása
ismeretlen üzenetformátum
Kiszolgálók mentése
Üdvözlőüzenet
@@ -1041,7 +1041,7 @@
A profilfrissítés el lesz küldve a partnerei számára.
Egyszerűsített inkognitómód
Menti az üdvözlőüzenetet?
- Új csevegési fiók létrehozásához indítsa újra az alkalmazást.
+ Új csevegési profil létrehozásához indítsa újra az alkalmazást.
Engedély megtagadva!
Függőben lévő hívás
Adatbázis megnyitása…
@@ -1049,7 +1049,7 @@
Jelmondat szükséges
Privát értesítések
Ön meghívta egy partnerét
- %s nincs hitelesítve
+ %s nincs ellenőrizve
Koppintson ide a kapcsolódáshoz
Ennek az eszköznek a neve
Jelenlegi profil
@@ -1081,7 +1081,7 @@
Újraindítás
SMP-kiszolgálók
Videó
- SimpleX-cím beállításainak mentése
+ SimpleX-címbeállítások mentése
Újraegyeztetés
Várakozás a videóra
Saját XFTP-kiszolgálók
@@ -1119,11 +1119,11 @@
Várakozás a képre
Hangüzenetek
Eltávolítja a tagot?
- Biztonsági kód hitelesítése
+ Biztonsági kód ellenőrzése
eltávolította Önt
SimpleX-cím
Megjelenítve:
- válasz fogadása…
+ válasz érkezett…
Visszaállítja az adatbázismentést?
Üzenetek fogadása…
%s és %s kapcsolódott
@@ -1160,7 +1160,7 @@
Kihagyott üzenetek
A hangüzenetek küldése le van tiltva.
Partner nevének beállítása
- Csak Ön tud eltűnő üzeneteket küldeni.
+ Csak Ön küldhet eltűnő üzeneteket.
Médiatartalom megosztása…
Ön: %1$s
Beállítások
@@ -1170,7 +1170,7 @@
A kapott hivatkozás beillesztése a partnerhez való kapcsolódáshoz…
Beolvasás
Port nyitása a tűzfalban
- indítás…
+ hívás indítása…
Leállítás
elküldve
SOCKS proxy használata
@@ -1217,7 +1217,7 @@
Rendszer-hitelesítés
Böngészőn keresztül
Védje meg a csevegési profiljait egy jelszóval!
- Csak a partnere tud eltűnő üzeneteket küldeni.
+ Csak a partnere küldhet eltűnő üzeneteket.
Saját ICE-kiszolgálók
QR-kód beolvasása a számítógépről
SimpleX logó
@@ -1239,7 +1239,7 @@
SimpleX-zár bekapcsolva
elküldés a partnernek
Beolvasás hordozható eszközről
- Kapcsolatok hitelesítése
+ Kapcsolatok ellenőrzése
Üzenet megosztása…
másodperc
A SimpleX-zár nincs bekapcsolva!
@@ -1248,7 +1248,7 @@
Csevegési adatbázis
eltávolította őt: %1$s
Sikertelen kiszolgáló teszt!
- Kapcsolat hitelesítése
+ Kapcsolat ellenőrzése
Tudjon meg többet
A fájl küldője visszavonta az átvitelt.
Megállítja a csevegést?
@@ -1256,7 +1256,7 @@
Beállítva 1 nap
Felfedés
Fogadott üzenetbuborék színe
- Csak a partnere tudja az üzeneteket véglegesen törölni (Ön csak törlésre jelölheti meg azokat). (24 óra)
+ Csak a partnere törölheti véglegesen az üzeneteket (Ön csak törlésre jelölheti meg azokat). (24 óra)
Az önmegsemmisítő jelkód módosult!
SimpleX Chat kiszolgálók használatban.
SimpleX Chat kiszolgálók használata?
@@ -1273,27 +1273,27 @@
Az üzenetváltás jövője
Módosítja a hálózati beállításokat?
Várakozás a hordozható eszköz társítására:
- Biztonságos kapcsolat hitelesítése
+ Biztonságos kapcsolat ellenőrzése
fájlok küldése egyelőre még nem támogatott
Ön módosította a címet %s számára
fájlok fogadása egyelőre még nem támogatott
Csoportprofil mentése
Visszaállítás alapértelmezettre
Hacsak a partnere nem törölte a kapcsolatot, vagy ez a hivatkozás már használatban volt egyszer, lehet hogy ez egy hiba – jelentse a problémát.\nA kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcsolattartási hivatkozást, és ellenőrizze, hogy a hálózati kapcsolat stabil-e.
- videóhívás (nem e2e titkosított)
+ videóhívás (végpontok között NEM titkosított)
Használat új kapcsolatokhoz
- Az új üzeneteket az alkalmazás időszakosan lekéri – naponta néhány százalékot használ az akkumulátorból. Az alkalmazás nem használ push-értesítéseket – az eszközről származó adatok nem lesznek elküldve a kiszolgálóknak.
+ Az új üzeneteket az alkalmazás időszakosan lekéri – naponta néhány százalékot használ az akkumulátorból. Az alkalmazás nem használ leküldéses értesítéseket – az eszközről származó adatok nem lesznek elküldve a kiszolgálóknak.
Számítógép címének beillesztése
a kapcsolattartási címhivatkozáson keresztül
- a SimpleX a háttérben fut a push értesítések használata helyett.]]>
+ a SimpleX a háttérben fut a leküldéses értesítések használata helyett.]]>
A partnereinek online kell lennie ahhoz, hogy a kapcsolat létrejöjjön.\nVisszavonhatja ezt a kapcsolatot és eltávolíthatja a partnert (ezt később ismét megpróbálhatja egy új hivatkozással).
A jelmondat nem található a Keystore-ban, ezért kézzel szükséges megadni. Ez akkor történhetett meg, ha visszaállította az alkalmazás adatait egy biztonsági mentési eszközzel. Ha nem így történt, akkor lépjen kapcsolatba a fejlesztőkkel.
- A partnerei továbbra is kapcsolódva maradnak.
+ A partnereivel továbbra is kapcsolatban marad.
A kiszolgálónak hitelesítésre van szüksége a feltöltéshez, ellenőrizze a jelszavát.
Az adatbázis nem működik megfelelően. Koppintson ide a további információkért
A fájl küldése le fog állni.
Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott partnerétől érkező üzenetek fogadására szolgál.
- Nem sikerült hitelesíteni; próbálja meg újra.
+ Nem sikerült ellenőrizni; próbálja meg újra.
Az üzenet az összes tag számára moderáltként lesz megjelölve.
Értesítések fogadásához adja meg az adatbázis jelmondatát
A teszt a(z) %s lépésnél sikertelen volt.
@@ -1329,14 +1329,14 @@
%1$s.]]>
Profil felfedése
Ez nem egy érvényes kapcsolattartási hivatkozás!
- A végpontok közötti titkosítás hitelesítéséhez hasonlítsa össze (vagy olvassa be a QR-kódot) a partnere eszközén lévő kóddal.
+ A végpontok közötti titkosítás ellenőrzéséhez hasonlítsa össze (vagy olvassa be a QR-kódot) a partnere eszközén lévő kóddal.
A csevegési adatbázis legfrissebb verzióját CSAK egy eszközön kell használnia, ellenkező esetben előfordulhat, hogy az üzeneteket nem fogja megkapni valamennyi partnerétől.
Ez a beállítás csak az Ön jelenlegi csevegési profiljában lévő üzenetekre vonatkozik
Ön meghívást kapott a csoportba. Csatlakozzon, hogy kapcsolatba léphessen a csoport tagjaival.
Ez a csoport már nem létezik.
A csatlakozás már folyamatban van a csoporthoz ezen a hivatkozáson keresztül.
Ön meghívást kapott a csoportba
- A partnere a jelenleg megengedett maximális méretű (%1$s) fájlnál nagyobbat küldött.
+ A partnere a jelenleg támogatott legnagyobb (%1$s) fájlméretnél nagyobbat küldött.
A partnerei és az üzenetek (kézbesítés után) nem a SimpleX kiszolgálókon vannak tárolva.
Üzenetek formázása a szövegbe szúrt speciális karakterekkel:
Megnyitás az alkalmazásban gombra.]]>
@@ -1348,14 +1348,14 @@
Átvitelelkülönítés
Akkor lesz kapcsolódva, ha a kapcsolódási kérését elfogadják, várjon, vagy ellenőrizze később!
A hangüzenetek küldése le van tiltva.
- Alkalmazás akkumulátor-használata / Korlátlan módot az alkalmazás beállításaiban.]]>
+ Alkalmazás akkumulátor-használata / Korlátlan módot az alkalmazás beállításaiban.]]>
Biztonságos kvantumbiztos protokollon keresztül.
- legfeljebb 5 perc hosszúságú hangüzenetek.\n- egyéni időkorlát beállítása az üzenetek eltűnéséhez.\n- előzmények szerkesztése.
Társítás számítógéppel menüt a hordozható eszköz alkalmazásban és olvassa be a QR-kódot.]]>
%s ekkor: %s
Akkor lesz kapcsolódva, amikor a partnerének az eszköze online lesz, várjon, vagy ellenőrizze később!
Kéretlen üzenetek elrejtése.
- Onion kiszolgálók használata opciót „Nemre”, ha a SOCKS proxy nem támogatja őket.]]>
+ Onion kiszolgálók használata beállítást „Nemre”, ha a SOCKS proxy nem támogatja őket.]]>
Megoszthatja a címét egy hivatkozásként vagy egy QR-kódként – így bárki kapcsolódhat Önhöz.
Létrehozás később
A profilja az eszközén van tárolva és csak a partnereivel van megosztva. A SimpleX kiszolgálók nem láthatják a profilját.
@@ -1366,7 +1366,7 @@
Csoportmeghívó elküldve
Frissíti az átvitelelkülönítési módot?
Átvitelelkülönítés
- Ettől a csoporttól nem fog értesítéseket kapni. A csevegési előzmények megmaradnak.
+ Nem fog több üzenetet kapni ebből a csoportból, de a csevegés előzményei megmaradnak.
A csevegési adatbázis nem titkosított – állítson be egy jelmondatot annak védelméhez.
Közvetlen internetkapcsolat használata?
Továbbra is kap hívásokat és értesítéseket a némított profiloktól, ha azok aktívak.
@@ -1389,8 +1389,8 @@
A beállítások frissítése a kiszolgálókhoz való újra kapcsolódással jár.
kapcsolatba akar lépni Önnel!
Ön a következőre módosította a saját szerepkörét: „%s”
- A csevegési szolgáltatás elindítható a „Beállítások / Adatbázis” menüben vagy az alkalmazás újraindításával.
- Kód hitelesítése a hordozható eszközön
+ A csevegés elindítható az alkalmazás „Beállítások / Adatbázis” menüjében vagy az alkalmazás újraindításával.
+ Kód ellenőrzése a hordozható eszközön
Ön csatlakozott ehhez a csoporthoz. Kapcsolódás a meghívó csoporttaghoz.
a SimpleX Chat fejlesztőivel, ahol bármiről kérdezhet és értesülhet a friss hírekről.]]>
Nem kötelező üdvözlőüzenettel.
@@ -1402,7 +1402,7 @@
%1$s nevű csoporthoz!]]>
A hangüzenetek küldése le van tiltva ebben a csevegésben.
Ön irányítja csevegését!
- Kód hitelesítése a számítógépen
+ Kód ellenőrzése a számítógépen
Az időzóna védelmének érdekében a kép-/hangfájlok UTC-t használnak.
A csatlakozási kérése el lesz küldve ennek a csoporttagnak.
Ha egy inkognitóprofilt oszt meg valamelyik partnerével, a rendszer ezt az inkognitóprofilt fogja használni azokban a csoportokban, ahová az adott partnere meghívja Önt.
@@ -1414,12 +1414,12 @@
A kézbesítési jelentések küldése az összes partnere számára engedélyezve lesz.
Protokoll időtúllépése kB-onként
Az adatbázis jelmondatának módosítására tett kísérlet nem fejeződött be.
- Ez a művelet nem vonható vissza – a kijelöltnél korábban küldött és fogadott üzenetek törölve lesznek. Ez több percet is igénybe vehet.
+ Ez a művelet nem vonható vissza – a kiválasztott üzenettől korábban küldött és fogadott üzenetek törölve lesznek. Ez több percet is igénybe vehet.
A profilja csak a partnereivel van megosztva.
Néhány kiszolgáló megbukott a teszten:
Koppintson ide a csatlakozáshoz
Ez a művelet nem vonható vissza – az összes fogadott és küldött fájl a médiatartalmakkal együtt törölve lesznek. Az alacsony felbontású képek viszont megmaradnak.
- A kézbesítési jelentések engedélyezve vannak %d partnernél
+ A kézbesítési jelentések engedélyezve vannak %d partner számára
Küldés a következőn keresztül:
Köszönet a felhasználóknak a Weblate-en való közreműködésért!
A kézbesítési jelentések küldése engedélyezve lesz az összes látható csevegési profilban lévő összes partnere számára.
@@ -1443,7 +1443,7 @@
Köszönet a felhasználóknak a Weblate-en való közreműködésért!
Jelmondat mentése a beállításokban
Ennek a csoportnak több mint %1$d tagja van, a kézbesítési jelentések nem lesznek elküldve.
- A második jelölés, amit kihagytunk! ✅
+ A második pipa, ami már nagyon hiányzott! ✅
A továbbítókiszolgáló megvédi az IP-címét, de megfigyelheti a hívás időtartamát.
Az utolsó üzenet tervezetének megőrzése a mellékletekkel együtt.
A mentett WebRTC ICE-kiszolgálók el lesznek távolítva.
@@ -1452,10 +1452,10 @@
Profil és kiszolgálókapcsolatok
Egy üzenetváltó- és alkalmazásplatform, amely védi az adatait és biztonságát.
Koppintson ide a profil aktiválásához.
- A kézbesítési jelentések le vannak tiltva %d partnernél
- Munkamenet kód
+ A kézbesítési jelentések le vannak tiltva %d partner számára
+ Munkamenet kódja
Köszönet a felhasználóknak a Weblate-en való közreműködésért!
- Kis csoportok (max. 20 tag)
+ Kis csoportok (legfeljebb 20 tag)
Az Ön által elfogadott kapcsolat vissza lesz vonva!
Élő üzenet küldése – az üzenet a címzett(ek) számára valós időben frissül, ahogy Ön beírja az üzenetet
A KÉZBESÍTÉSI JELENTÉSEKET A KÖVETKEZŐ CÍMRE KELL KÜLDENI
@@ -1519,7 +1519,7 @@
Számítógép elfoglalt
Számítógép inaktív
Csevegés újraindítása
- Időtúllépés a számítógéphez való csatlakozáskor
+ Időtúllépés a számítógéphez való társításkor
A számítógép le lett választva
A kapcsolat megszakadt
A kapcsolat megszakadt
@@ -1555,7 +1555,7 @@
Privát jegyzetek
Hiba történt a privát jegyzetek törlésekor
Hiba történt az üzenet létrehozásakor
- Kiüríti a privát jegyzeteket?
+ Üríti a privát jegyzetek tartalmát?
Létrehozva
Mentett üzenet
Létrehozva: %s
@@ -1586,7 +1586,7 @@
Az üdvözlőüzenet túl hosszú
Az adatbázis átköltöztetése folyamatban van.\nEz eltarthat néhány percig.
Hanghívás
- A hívás befejeződött
+ Hívás vége
Videóhívás
Hiba történt a böngésző megnyitásakor
A hívásokhoz egy alapértelmezett webböngésző szükséges. Állítson be egy alapértelmezett webböngészőt az eszközön, és osszon meg további információkat a SimpleX Chat fejlesztőivel.
@@ -1613,14 +1613,14 @@
Hiba történt a beállítások mentésekor
Hiba történt az archívum letöltésekor
Hiba történt az archívum feltöltésekor
- Hiba történt a jelmondat hitelesítésekor:
+ Hiba történt a jelmondat ellenőrzésekor:
Az exportált fájl nem létezik
A fájl törölve lett, vagy érvénytelen a hivatkozás
%s letöltve
Archívum importálása
Feltöltés előkészítése
- Az adatbázis jelmondatának hitelesítése
- Jelmondat hitelesítése
+ Adatbázis jelmondatának ellenőrzése
+ Jelmondat ellenőrzése
Jelmondat beállítása
Kép a képben hívások
Biztonságosabb csoportok
@@ -1633,10 +1633,10 @@
A folytatáshoz a csevegést meg kell szakítani.
Csevegés megállítása folyamatban
Vagy ossza meg biztonságosan ezt a fájlhivatkozást
- Csevegés indítása
+ Csevegés elindítása
Nem szabad ugyanazt az adatbázist használni egyszerre két eszközön.]]>
Az átköltöztetéshez erősítse meg, hogy emlékszik az adatbázis jelmondatára.
- Átköltöztetés egy másik eszközről opciót az új eszközén és olvassa be a QR-kódot.]]>
+ Átköltöztetés egy másik eszközről beállítást az új eszközén és olvassa be a QR-kódot.]]>
Átköltöztetés véglegesítése
Átköltöztetés véglegesítése egy másik eszközön.
Letöltés előkészítése
@@ -1739,10 +1739,10 @@
Nem
Nem védett
Igen
- NE használjon privát útválasztást.
+ NE legyen használva privát útválasztás.
Privát útválasztás
Privát útválasztás használata az ismeretlen kiszolgálókhoz.
- Mindig használjon privát útválasztást.
+ Mindig legyen használva privát útválasztás.
Üzenet-útválasztási mód
Közvetlen üzenetküldés, ha az IP-cím védett és a saját kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást.
Közvetlen üzenetküldés, ha a saját kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást.
@@ -1816,7 +1816,7 @@
Ezt a hivatkozást egy másik hordozható eszközön már használták, hozzon létre egy új hivatkozást a számítógépén.
Ellenőrizze, hogy a hordozható eszköz és a számítógép ugyanahhoz a helyi hálózathoz csatlakozik-e, valamint a számítógép tűzfalában engedélyezve van-e a kapcsolat.\nMinden további problémát osszon meg a fejlesztőkkel.
Nem lehet üzenetet küldeni
- A kijelölt csevegési beállítások tiltják ezt az üzenetet.
+ A kiválasztott csevegési beállítások tiltják ezt az üzenetet.
Próbálja meg később.
A kiszolgáló címe nem kompatibilis a hálózati beállításokkal: %1$s.
Inaktív tag
@@ -1843,7 +1843,7 @@
Újrakapcsolódás az összes kiszolgálóhoz
Hiba történt a statisztikák visszaállításakor
Visszaállítás
- Az összes statisztika visszaállítása
+ Összes statisztika visszaállítása
Visszaállítja az összes statisztikát?
A kiszolgálók statisztikái visszaállnak – ez a művelet nem vonható vissza!
Részletes statisztikák
@@ -1976,12 +1976,12 @@
Engedélyeznie kell a hívásokat a partnere számára, hogy fel tudják hívni egymást.
A(z) %1$s nevű partnerével folytatott beszélgetéseit továbbra is megtekintheti a csevegések listájában.
Üzenet…
- Kijelölés
+ Kiválasztás
Az üzenetek az összes tag számára moderáltként lesznek megjelölve.
- Nincs semmi kijelölve
+ Nincs semmi kiválasztva
Az üzenetek törlésre lesznek jelölve. A címzett(ek) képes(ek) lesz(nek) felfedni ezt az üzenetet.
Törli a tagok %d üzenetét?
- %d kijelölve
+ %d kiválasztva
Az üzenetek az összes tag számára törölve lesznek.
Csevegési adatbázis exportálva
Kapcsolatok- és kiszolgálók állapotának megjelenítése.
@@ -2024,7 +2024,7 @@
CSEVEGÉSI ADATBÁZIS
Profil megosztása
Rendszerbeállítások használata
- Csevegési profil kijelölése
+ Csevegési profil kiválasztása
Ne használja a hitelesítési adatokat proxyval.
Különböző proxy-hitelesítési adatok használata az összes profilhoz.
Különböző proxy-hitelesítési adatok használata az összes kapcsolathoz.
@@ -2048,7 +2048,7 @@
%1$s üzenet nem lett továbbítva
Továbbít %1$s üzenetet?
Továbbítja az üzeneteket fájlok nélkül?
- Az üzeneteket törölték miután kijelölte őket.
+ Az üzeneteket törölték miután kiválasztotta őket.
%1$s üzenet mentése
Hiba történt az üzenetek továbbításakor
Hang elnémítva
@@ -2060,8 +2060,8 @@
Minden alkalommal, amikor elindítja az alkalmazást, új SOCKS-hitelesítési adatok lesznek használva.
Alkalmazás munkamenete
Az összes kiszolgálóhoz új, SOCKS-hitelesítési adatok lesznek használva.
- Kattintson a címmező melletti info gombra a mikrofon használatának engedélyezéséhez.
- Nyissa meg a Safari Beállítások / Weboldalak / Mikrofon menüt, majd válassza a helyi kiszolgálók engedélyezése lehetőséget.
+ Kattintson a címmező melletti információ gombra a mikrofon használatának engedélyezéséhez.
+ Nyissa meg a Safari / Beállítások / Weboldalak / Mikrofon menüt, majd válassza a helyi kiszolgálók engedélyezése beállítást.
Hívások kezdeményezéséhez engedélyezze a mikrofon használatát. Fejezze be a hívást, és próbálja meg a hívást újra.
Továbbfejlesztett hívásélmény
Továbbfejlesztett üzenetdátumok.
@@ -2178,7 +2178,7 @@
Értesítések és akkumulátor
Az alkalmazás mindig fut a háttérben
Elhagyja a csevegést?
- Ön nem fog több üzenetet kapni ebből a csevegésből, de a csevegés előzményei megmaradnak.
+ Nem fog több üzenetet kapni ebből a csevegésből, de a csevegés előzményei megmaradnak.
Csevegés törlése
Meghívás a csevegésbe
Barátok hozzáadása
@@ -2236,7 +2236,7 @@
Nincsenek olvasatlan csevegések
Lista létrehozása
Lista mentése
- Az összes csevegés el lesz távolítva a következő listáról, és a lista is törlődik: %s
+ Az összes csevegés el lesz távolítva a(z) %s nevű listáról, és a lista is törölve lesz
Törlés
Törli a listát?
Szerkesztés
@@ -2293,7 +2293,7 @@
alapértelmezett (%s)
Csevegési üzenetek törlése az eszközről.
Módosítja az automatikus üzenettörlést?
- Ez a művelet nem vonható vissza – a kijelölt üzenettől korábban küldött és fogadott üzenetek törölve lesznek a csevegésből.
+ Ez a művelet nem vonható vissza – a kiválasztott üzenettől korábban küldött és fogadott üzenetek törölve lesznek a csevegésből.
A következő TCP-port használata, amikor nincs port megadva: %1$s.
TCP-port az üzenetváltáshoz
Webport használata
@@ -2301,7 +2301,7 @@
Összes némítása
Legfeljebb %1$s tagot említhet meg egy üzenetben!
Az üzenetek jelentése a moderátorok felé engedélyezve van.
- Az üzenetek a moderátorok felé történő jelentésének megtiltása.
+ Az üzenetek jelentése a moderátorok felé le van tiltva.
Archiválja az összes jelentést?
Archivál %d jelentést?
Csak magamnak
@@ -2393,7 +2393,7 @@
csatlakozási kérés elutasítva
Ön elhagyta a csoportot
a tag régi verziót használ
- Hiba a csevegés törlésekor
+ Hiba történt a csevegés törlésekor
Ön nem tud üzeneteket küldeni!
a partner nem áll készen
nincs szinkronizálva
@@ -2450,9 +2450,9 @@
TCP-kapcsolat időtúllépése a háttérben
Profil betöltése…
Rövid leírás:
- Saját névjegy:
- Névjegy:
- A névjegy túl hosszú
+ Saját életrajz:
+ Életrajz:
+ Az életrajz túl hosszú
A leírás túl hosszú
Partneri kapcsolatkérés elfogadása
Üzleti kapcsolat
@@ -2468,7 +2468,7 @@
Saját cím létrehozása
Eltűnő üzenetek engedélyezése alapértelmezetten.
Tartsa tisztán a csevegéseit
- Névjegy és üdvözlőüzenet beállítása a profilokhoz.
+ Életrajz és üdvözlőüzenet beállítása a profilokhoz.
Saját cím megosztása
Rövid SimpleX-cím
Cím frissítése
@@ -2503,6 +2503,23 @@
A célkiszolgáló címében szereplő ujjlenyomat nem egyezik a tanúsítvánnyal: %1$s.
A továbbítókiszolgáló címében szereplő ujjlenyomat nem egyezik a tanúsítvánnyal: %1$s.
A kiszolgáló címében szereplő ujjlenyomat nem egyezik a tanúsítvánnyal: %1$s.
- nincs előfizetés
- Ön nem kapcsolódott ahhoz a kiszolgálóhoz, amely az adott partnerétől érkező üzenetek fogadására szolgál (nincs előfizetés).
+ nincs feliratkozás
+ Ön nem kapcsolódott ahhoz a kiszolgálóhoz, amely az adott partnerétől érkező üzenetek fogadására szolgál (nincs feliratkozás).
+ Tag üzeneteinek törlése
+ Törli a tag üzeneteit?
+ Üzenetek törlése
+ A tag üzenetei törölve lesznek – ez a művelet nem vonható vissza!
+ Eltávolítás és az üzeneteinek törlése
+ Összes üzenet
+ Fájlok
+ Szűrő
+ Képek
+ Hivatkozások
+ Fájlok keresése
+ Képek keresése
+ Hivatkozások keresése
+ Videók keresése
+ Hangüzenetek keresése
+ Videók
+ Hangüzenetek
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_image_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_image_filled.svg
new file mode 100644
index 0000000000..045484d0a1
--- /dev/null
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_image_filled.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_photo_library.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_photo_library.svg
new file mode 100644
index 0000000000..091b0d4692
--- /dev/null
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_photo_library.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_photo_library_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_photo_library_filled.svg
new file mode 100644
index 0000000000..72692b2e17
--- /dev/null
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_photo_library_filled.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml
index 909c6c7cfe..2257d93efa 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml
@@ -2509,4 +2509,9 @@
Sidik jari di alamat server tidak cocok dengan sertifikat: %1$s.
tidak berlangganan
Anda tidak terhubung ke server yang digunakan untuk menerima pesan dari koneksi ini (tidak berlangganan).
+ Hapus pesan anggota
+ Hapus pesan anggota?
+ Hapus pesan
+ Pesan anggota akan dihapus - ini tidak dapat dibatalkan!
+ Hapus pesan
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml
index 1c7e39d51e..1c191a78bd 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml
@@ -2452,7 +2452,7 @@
Entra nel gruppo
Apri la chat
Apri una chat nuova
- Apri un gruppo nuovo
+ Apri il nuovo gruppo
Apri per accettare
Apri per connettere
Apri per entrare
@@ -2541,4 +2541,21 @@
L\'impronta digitale nell\'indirizzo del server non corrisponde al certificato: %1$s.
nessuna iscrizione
Non sei connesso/a al server usato per ricevere messaggi da questa connessione (nessuna iscrizione).
+ Elimina i messaggi del membro
+ Eliminare i messaggi del membro?
+ Elimina i messaggi
+ I messaggi del membro verranno eliminati. Non è reversibile!
+ Rimuovi ed elimina i messaggi
+ Tutti i messaggi
+ File
+ Immagini
+ Link
+ Cerca file
+ Cerca immagini
+ Cerca link
+ Cerca video
+ Cerca messaggi vocali
+ Video
+ Messaggi vocali
+ Filtro
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml
index 6b413c9bfa..fb83b83735 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml
@@ -2138,4 +2138,17 @@
פתח שיחה חדשה
פתח קבוצה חדשה
שלח את המשוב הפרטי שלך לקבוצות.
+ הסכמה לבקשת חבר
+ הערות
+ לא נבחר כלום להעברה!
+ התראות וסוללה
+ הוסף הודעה
+ אפשר קבצים ומדיה רק כאשר החבר מאשר אותם
+ אפשר לאנשי קשר שלך לשלוח קבצים ומדיה
+ אודות:
+ האודות ארוך מדי
+ בוט
+ אתה והאיש קשר שלך יכולים לשלוח קבצים ומדיה
+ צ\'אט עסקי
+ אי אפשר לשנות תמונת פרופיל
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml
index cc85e49a8c..1c4d265515 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml
@@ -347,7 +347,7 @@
データベースパスフレーズ
データベースをエクスポート
データベースを削除
- データベースを読み込みますか?
+ データベースのインポート
新しいデータベースのアーカイブ
過去のデータベースアーカイブ
ファイルを全て削除
@@ -2042,4 +2042,17 @@
プライベートメッセージルーティング用のサーバーがありません。
メディアおよびファイルサーバーは存在しません。
ファイルを送信するサーバーがありません。
+ ソーシャルメディア向け
+ サーバを利用する
+ あなたのサーバ
+ ビデオ
+ ファイル
+ 画像
+ リンク
+ すべて
+ 音声メッセージ
+ フィルター
+ メンバーとして承認する
+ オブザーバーとして承認する
+ スパム
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ku/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ku/strings.xml
new file mode 100644
index 0000000000..0ea9328085
--- /dev/null
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/ku/strings.xml
@@ -0,0 +1,837 @@
+
+
+ Profîla niha bişuxulîne
+ Komê veke
+ Komeke nû veke
+ Lînka xelet
+ Databas tê vekirin…
+ xeletî
+ Profîl nikarîbû were çêkirin!
+ Profîl nikarîbû were guhertin!
+ Ti serverên medya & dosyayan nînin.
+ Ji min re
+ Ji hemû moderatoran re
+ Xeletî: %1$s
+ Cewab bide
+ Kopî bike
+ Qeyd bike
+ Biguhere
+ Melûmat
+ Lê bigere
+ Li sûretan bigere
+ Li vîdyoyan bigere
+ Li dosyayan bigere
+ Li lînkan bigere
+ Sûret
+ Vîdyo
+ Dosya
+ Lînk
+ Tarîx
+ Tarîx nîne
+ Cewab ji bo
+ Qeydkirî
+ Hatiye qeydkirin ji
+ Jê bibe
+ Veşêre
+ Bihêle
+ Gilî bike
+ Hilbijêre
+ Mezin bike
+ Şandina dosyayê bisekinîne?
+ Şandina dosyayê wê bê sekinandin.
+ Standina dosyayê bisekinîne?
+ Bisekinîne
+ Dosya wê ji serveran bê jêbirin.
+ Daxe
+ Lîste
+ Endam ne aktîv e
+ guhertî
+ şandî
+ şandin bi ser neket
+ nexwendî
+ Bi xêr hatî %1$s!
+ Bi xêr hatî!
+ Ev nivîs di eyaran de heye
+ Eyar
+ Bi navê %s bikeviyê
+ redkirî
+ Hemû
+ Lîste lê zêde bike
+ 1 gilîkirin
+ %d gilîkirin
+ Gilîkirinên endaman
+ Zêde sûret hene!
+ Zêde vîdyo hene!
+ Tenê 10 sûret karin di derbekê de werin şandin
+ Tenê 10 vîdyo karin di derbekê de werin şandin
+ Xeletiya dekodkirinê
+ Sûret nikare were dekodkirin. Bi xêra xwe, sûretekî dî biceribîne yan jî xeberê bide mielifan.
+ Dosya û medya memnû in!
+ Tenê xwediyên koman karin dosya û medya aktîv bikin.
+ Lînkên SimpleXê memnû in
+ Xwestinê bişîne
+ xwestin şandî ye
+ ne sinkronîzekirî ye
+ Bi xêra xwe xeberê bide admînê komê.
+ kom jêbirî ye
+ ji komê derxistî
+ tu derketî
+ Sûret
+ Li hêviya sûret e
+ Sûret hat şandin
+ Li hêviya sûret e
+ Vîdyo
+ Li hêviya vîdyo ye
+ Vîdyo hat şandin
+ Li hêviya vîdyoyê ye
+ Dosya
+ Koda emniyetê tesdîq bike
+ Kamera
+ Ji Galeriyê
+ Dosya
+ Dosyakê hilbijêre
+ Sûret
+ Vîdyo
+ Pêl pişkokê bike
+ li ser, piştre:
+ Komekê çêke: ji bo çêkirina komeke nû.]]>
+ Qebûl bike
+ Red bike
+ Heya kesê ko şandiye jê çênabe.
+ Endam hatiye jêbirin - nikare xwestinê qebûl bike
+ Jê bibe
+ Jê bibe
+ Xwendî nîşan bide
+ Nexwendî nîşan bide
+ Bêdeng bike
+ Hemûka bêdeng bike
+ Bêdengkirinê betal bike
+ Bike favorît
+ Ji favorîtan derxe
+ Behsên nexwendî
+ Lîste çêke
+ Li lîstê zêde bike
+ Lîstê biguhere
+ Lîstê qeyd bike
+ Navê lîstê...
+ Navê lîstê û emojiya wê divê ji bo her lîsteyî cuda be.
+ Jê bibe
+ Lîstê jê bibe?
+ Biguhere
+ Rêzê biguhere
+ sûretê profîlê
+ Pişkoka girtinê
+ Eyar
+ Koda QRyê
+ Adresa SimpleXê
+ arîkarî
+ Taximê SimpleXê
+ Logoya SimpleXê
+ E-poste
+ Bêhtir
+ Koda QRyê nîşan bide
+ Lînka 1-carê bi hevalekî re parve bike
+ Ji bo ko tu xwe ji guhertina lînka biparêzî, tu karî kodên emniyetê yên kontaktê qiyas bikî.
+ Yan jî vê kodê nîşan bide
+ Lînka timam
+ Lînka kurt
+ Profîlê parve bike
+ Profîl nikarîbû were guhertin
+ Yan jî koda QRyê skan bike
+ Dewetiya neşuxulandî bihêle?
+ Bihêle
+ Lînk tê çêkirin…
+ Dîsa biceribîne
+ Vê lînka 1-carê parve bike
+ Lînka ko te standiye bizeliqîne
+ Nivîsa ko te zeliqand ne lînkeke SimpleXê ye.
+ Pêl vir bike ji bo zeliqandina lînkê
+ Profîla te
+ Profîl nikare were guhertin
+ Kod skan bike
+ Koda emniyetê xelet e!
+ Koda emniyetê
+ Tesdîqkirî nîşan bide
+ Tesdîqkirinê jê bibe
+ %s tesdîqkirî ye
+ %s ne tesdîqkirî ye
+ Eyarên te
+ Adresa te yî SimpleXê
+ Li ser SimpleX Chat
+ Çawa tê şuxulandin
+ Arîkariya Markdownê
+ Pirsan û fikran bişîne
+ E-poste ji me re bişîne
+ Server bi kar bîne
+ Serverên SimpleX Chat tên şuxulandin.
+ Çilo
+ Çilo yek serverên xwe dişuxulîne
+ Mecbûrî
+ Neparastî
+ Ne ti carî
+ Erê
+ Wextê ko IP veşartî ye
+ Na
+ Girtî
+ Saxlem
+ Beta
+ %s (%s) daxe
+ Cihê dosyayê veke
+ Dûvre bîne bîra min
+ Adresê jê bibe?
+ Lînkê parve bike
+ Adresa SimpleXê çêke
+ Hew adresê parve bike?
+ Hew parve bike
+ Qebûlkirina ji ber xwe ve
+ Eyaran qeyd bike?
+ Eyarên adresa SimpleXê qeyd bike
+ Adresê jê bibe
+ Hevalan dewet bike
+ Em li SimpleX Chat qise bikin
+ Ji bo medyaya sosyal
+ Yan ji bo parvekirina şexsî
+ Adresa SimpleXê yan jî lînka 1-carê?
+ Lînka 1-carê çêke
+ Eyarên adresê
+ Sûret jê bibe
+ Tercihan qeyd bike?
+ Bê qeydkirinê derkeve
+ Profîlê veşêre
+ Şîfra profîlê qeyd bike
+ Li ser SimpleXê
+ Markdown çawa tê şuxulandin
+ qalin
+ xwehr
+ a + b
+ bi reng
+ tê telefonkirin…
+ Kamera
+ Kamera û mîkrofon
+ Van destûran bide ji bo telefonkirinê
+ Di eyaran de destûrê bide
+ Vê destûrê di eyarên Androidê de bibîne û bixwe destûrê bide.
+ Eyaran veke
+ Bluetooth
+ Profîla xwe çêke
+ Çawa dişuxule
+ SimpleX çawa dişuxule
+ Çawa tesîrê li pîlê dike
+ Her serê pêlekê
+ Di cih de
+ Notîfîkasyon û pîl
+ Tu karî serveran ji eyaran eyar bikî.
+ Dewam bike
+ Qebûl bike
+ Red bike
+ Qebûl bike
+ Nîşan bide
+ Eyar nikarîbû were guhertin
+ Dewam bike
+ Şîfrê ji eyaran bibe?
+ Jê bibe
+ Şîfra niha…
+ Şîfra nû…
+ Endaman dewet bike
+ Çêtir tecrûba karber
+ Adresa xwe parve bike
+ saniye
+ deqe
+ seet
+ roj
+ heftî
+ heyv
+ Hilbijêre
+ Telefonekê girê de
+ Telefonên girêdayî
+ Ji telefonê skan bike
+ Navê vê cihazê
+ (ev cihaz v%s)]]>
+ Telefona girêdayî
+ Girêdayî telefonê
+ Navê vê cihazê binivîsîne…
+ Xeletî
+ Ev cihaz
+ Cihaz
+ Cihaza mobîlê yî nû
+ %s hat qutkirin]]>
+ Girêdan sekinî
+ Girêdan sekinî
+ Hemû statîstîkan vala bike
+ Serveran qeyd bike?
+ Skan bike / Lînk bizeliqîne
+ Koda QRyê skan bike
+ Ji kompîterê koda QRyê skan bike
+ Koda QRyê ya serverê skan bike
+ %s (niha)
+ %s daxistî
+ lê bigere
+ Lê bigere yan jî lînka SimpleXê bizeliqîne
+ san
+ Ê diwan
+ Dora emîn
+ koda emniyetê hat guhertin
+ Xeletiyên şandinê
+ şandina dosyayan hê ne mimkun e
+ Tê şandin bi riya
+ Pêşdîtinên lînkan bişîne
+ Gilîkirinên şexsî bişîne
+ Wextê şandinê:
+ Cewaba şandî
+ Bi riya proksiyê şandî
+ Server
+ Adresa serverê
+ Adres
+ Adresa serverê li eyarên torê nayê.
+ SERVER
+ Melûmata serveran
+ Ceribandina serverê bi ser neket!
+ Versiyona serverê li eyarên torê nayê.
+ 1 roj deyne
+ Tercihên komê diyar bike
+ EYAR
+ Parve bike
+ Lînka 1-carê parve bike
+ Adresê parve bike
+ Adresê bi hişkereyî parve bike
+ Dosya parve bike…
+ Medya parve bike…
+ Adresa kevn parve bike
+ Lînka kevn parve bike
+ Adresa SimpleXê li medayaya sosyal parve bike.
+ Behsa kin:
+ Adresa SimpleXê yî kin
+ Nîşan bide:
+ Pêşdîtinê nîşan bide
+ Bigire
+ Bigire?
+ SimpleX
+ Adresa SimpleXê
+ Lînka qenala SimpleXê
+ Xizmeta SimpleX Chatê
+ Lînka komê ya SimpleXê
+ Lînkên SimpleXê
+ Lînkên SimpleXê
+ Lînkên SimpleXê memnû in.
+ simplexmq: v%s (%2s)
+ Dewetiya yek carê ya SimpleXê
+ Mezinbûnî
+ Ser bakirina endaman ve derbas bibe
+ Funksiyona hêdî
+ Komên piçûk (herî zêde 20)
+ Servera SMPyê
+ Serverên SMPyê
+ Nerm
+ Bêdeng
+ Spam
+ Spam
+ Çarçik, girover, yan çi tiştê di neqebê de.
+ %s: %s
+ %s saniye
+ %s server
+ Li GitHubê stêrkê bide
+ dest pê dike…
+ Her serê pêlekê dest pê dike
+ Statîstîk
+ Bisekinîne
+ Dosyayê bisekinîne
+ xet/xêz/xîşk
+ Biqewet
+ Abonekirî
+ PIŞT BIDE SIMPLEX CHATÊ
+ Biguhere
+ Sîstem
+ Sîstem
+ Sîstem
+ Sîstem
+ Moda sîstemê
+ Terî/Dûvik
+ Pêl Adresa SimpleXê çêke di meniwê de ji bo ko tu dûvre çêkî.
+ Pêl Bikeve komê bike
+ Bikeviyê
+ Bikeve komê
+ Bikeve komê?
+ Bikeve komê?
+ Dikeve komê
+ Bikeve koma xwe?
+ %1$d dosya hê tê(n) daxistin.
+ %1$d dosya nikarîbû(n) wer(e/in) daxistin.
+ %1$d dosya hat(in) jêbirin.
+ %1$d dosya nehat(in) daxistin.
+ %1$d xeletiyên dosyayê ên dî.
+ %1$s ENDAM
+ 1 roj
+ 1 deqe
+ 1 heyv
+ lînka 1-carê
+ 1 heftî
+ 1 sal
+ 30 saniye
+ 5 deqe
+ Betal bike
+ Guhertina adresê betal bike
+ Guhertina adresê betal bike?
+ Li ser adresa SimpleXê
+ Qebûl bike
+ Qebûl bike
+ Qebûl bike
+ Wek endamekî qebûl bike
+ Şertan qebûl bike
+ %1$s hat qebûlkirin
+ Şertên qebûlkirî
+ dewetiyê qebûl kir
+ tu qebûl kirî
+ Endam qebûl bike
+ Girêdanên aktîv
+ Hevalan lê zêde bike
+ Guhertina adresê wê bê betalkirin. Adresa berê yî standinê wê bê şuxulandin.
+ Adres yan jî lînka 1-carê?
+ Serverekê lê zêde bike
+ Bi riya skankirina kodên QRyê serveran lê zêde bike.
+ Endamên têxim lê zêde bike
+ Cihazeke dî lê zêde bike
+ admîn
+ admîn
+ Admîn karin endamekî ji bo her kesî blok bikin.
+ Admîn karin lînkên lêzêdebûna koman çêkin.
+ Eyarên torê ên pêşketî
+ Eyarên pêşketî
+ Eyarên pêşketî
+ Hinek tiştên dî
+ hemû
+ Hemû dataya aplîkasyonê hat jêbirin.
+ hemû endam
+ Bihêle
+ Bihêle ko dosya û medya werin şandin.
+ Bihêle ko lînkên SimpleXê werin şandin.
+ Hemû profîl
+ Hemû server
+ Jixwe tê girêdan!
+ Jixwe dikeve komê!
+ hercar
+ Hercar
+ Hercar vekirî
+ û %d hewadîsên dî
+ Biguhere
+ Şîfra databasê biguhere?
+ rola %s hat guhertin %s
+ rola te hat guhertin %s
+ Rola komê biguhere?
+ Adresa standinê biguhere
+ Adresa standinê biguhere?
+ Rolê biguhere
+ adres tê guhertin…
+ adres tê guhertin…
+ adresa %s tê guhertin…
+ %1$d xeletiyên dosyayan:\n%2$s
+ %1$s dixwaze bi te re bikeve danûstandinê bi riya
+ Adresa serverê kontrol bike û dîsa biceribîne.
+ Girêdana xwe yî înternetê kontrol bike û dîsa biceribîne
+ Notên şexsî vala bike?
+ Pêl pişkoka melûmatê ya nêzîkî cihê adresê bike ji bo destûrdana mîkrofonê.
+ Moda reng
+ Di wextekî nêzîk de tê!
+ Dosya qiyas bike
+ timam
+ Timam bûye
+ Vala bike
+ Vala bike
+ Vala bike
+ Şert di %s de hatin qebûlkirin.
+ Şertên şuxulandinê
+ Şert wê di %s de bên qebûlkirin.
+ Serverên SMPyê ên eyarkirî
+ Serverên XFTPyê ên eyarkirî
+ Serverên ICEyê eyar bike
+ Dosyayên ji serverên nenas qebûl bike.
+ Eyarên torê tesdîq bike.
+ Şîfra nû dîsa binivîsîne…
+ Bi xwe re bikeve danûstandinê?
+ Bi riya lînkê bikeve danûstandinê
+ Bi riya lînkê bikeve danûstandinê?
+ Bi riya lînkê / koda QRyê bikeve danûstandinê
+ Bi riya lînka yek carê bikeve danûstandinê?
+ Bi %1$s re bikeve danûstandinê?
+ Muhtewa ne li gora şertên şuxulandinê ye
+ Îkona kontekstê
+ Dewam bike
+ Beşdar bibe
+ Tora xwe kontrol bike
+ Xeletiyê kopî bike
+ Çêke
+ Çêke
+ Adres çêke
+ Adresekê çêke ji bo ko xelk karibin bi te re bikevin danûstandinê.
+ Hat çêkirin
+ Wextê çêkirinê
+ Wextê çêkirinê: %s
+ Dosya çêke
+ Kom çêke
+ Lînka komê çêke
+ Lînk çêke
+ Lînkeke dewetiyê ya yek carî çêke
+ Profîl çêke
+ Profîl çêke
+ Dor çêke
+ Komeke veşartî çêke
+ Komeke veşartî çêke
+ Adresa xwe çêke
+ Lînka arşîvê tê çêkirin
+ kesê ko çêkiriye
+ Xeletiya cidî
+ (niha)
+ Profîla niha
+ Tarî
+ Tarî
+ Moda tarî
+ Rengên moda tarî
+ Xuyakirina tarî
+ IDya databasê
+ IDya databasê: %d
+ %dr
+ %d roj
+ %d roj
+ jiberxweve (%s)
+ jiberxweve (%s)
+ Jê bibe piştî
+ Hemû dosyayan jê bibe
+ Jêbirî
+ Wextê jêbirinê
+ Wextê jêbirinê: %s
+ Dosya jê bibe
+ Ji bo min jê bibe
+ Komê jê bibe
+ Komê jê bibe?
+ Lînkê jê bibe
+ Lînkê jê bibe?
+ Profîlê jê bibe
+ Dorê jê bibe
+ Serverê jê bibe
+ Xeletiyên jêbirinê
+ Gihan/Gihiştin
+ Cihazên kompîter
+ Kompîter mijûl e
+ Kompîter ne aktîv e
+ Girêdana bi kompîterê re qut bû
+ Detay
+ CIHAZ
+ %d dosya bi mezibnbûniya timam ya %s
+ %d hewadîsên komê
+ %d seet
+ %d seet
+ Bigire
+ girtî
+ girtî
+ Ji bo her kesî bigire
+ Ji bo hemû koman bigire
+ %d deqe
+ %d deqe
+ %d heyv
+ %d heyv
+ %d heyv
+ Adres çêneke
+ Dîsa nîşan nede
+ Daxe
+ Daxistî
+ Dosyayên daxistî
+ Xeletiyên daxistinê
+ Daxistin bi ser neket
+ Dosya daxe
+ Detayên lînkê tên daxistin
+ %d heftî
+ Profîla komê biguhere
+ Sûret biguhere
+ Veke
+ vekirî
+ Vekirî heta
+ ji te re vekirî
+ Ji bo her kesî veke
+ Ji bo hemû koman veke
+ xilasbûyî
+ Navê komê binivîsîne:
+ Şîfra rast binivîsîne.
+ Şîfrê binivîsîne
+ Şîfrê binivîsîne…
+ Di lêgeranê de şîfrê binivîsîne
+ Navê xwe binivîsîne:
+ Xeletî
+ Xeletî
+ Xeletî
+ Xeletî di betalkirina guhertina adresê de
+ Xeletî di qebûlkirina şertan de
+ Xeletî di qebûlkirina xwestina ketina danûstandinê de
+ Xeletî di qebûlkirina endêm de
+ Xeletî di lêzêdekirina endam(an) de
+ Xeletî di lêzêdekirina serverê de
+ Xeletî di guhertina adresê de
+ Xeletî di guhertina profîlê de
+ Xeletî di guhertina rolê de
+ Xeletî di çêkirina adresê de
+ Serverên te yên XFTPyê
+ Te dewetîke komê şand
+ Serverên te yên SMPyê
+ Serverên te
+ Adresa servera te
+ Servera te
+ Profîla te yî %1$s wê bê parvekirin.
+ Tercihên te
+ Serverên te yên ICEyê
+ Serverên te yên ICEyê
+ Koma te
+ te %1$s derxist
+ Te dewetiya komê red kir
+ Profîla te yî niha
+ Tu karî dûvre wê çêkî
+ Tu dikarî wê di Eyarên xuyakirinê de biguherî.
+ te %s blok kir
+ Tu hatiye dewetkirinî komê
+ Tu jixwe dikevî vê komê bi riya vê lînkê.
+ Tu dihêlî
+ te ev endam qebûl kir
+ tu: %1$s
+ TU
+ tu
+ Erê
+ erê
+ Serverên XFTPyê
+ Servera XFTPyê
+ Şîfra xelet!
+ Şîfra xelet ya databasê
+ Bi kêmtir xerckirina pîlê.
+ Bi kêmtir xerckirina pîlê.
+ Bê Tor yan VPNê, adresa te yî IPyê wê ji van relayên XFTPyê re xuya bike:\n%1$s,
+ Bê Tor yan jî VPNê, wê adresa te yî IPyê ji serverên dosyayen re xuya bike.
+ Etherneta bi qeblo
+ WiFi
+ Çi yî nû heye
+ Websîte
+ Serverên WebRTC ICEyê
+ Hişyarî: hinek dataya te kare winda bibe!
+ dixwaze bi te re bikeve danûstandinê!
+ Girtî
+ Bi xêra xwe aplîkasyonê ji nû ve veke.
+ Veşêre:
+ Navê profîlê:
+ Navê timam:
+ Qeyd bike û xeberê bide endamên komê
+ Şîfra nîşandanê
+ Şîfra profîla veşartî
+ Tu karî markdownê bişuxulînî ji bo formatkirina mesajan:
+ Bi şuxulandina SimpleX Chatê tu qebûl dikî ku tu:\n- di komên vekirî tenê muhtewaya qanûnî bişînî.\n- hurmeta karberên dî bigirî – spam çênabe.
+ Veke
+ bi riya relayê
+ Vidyo girtî
+ Vîdyo vekirî
+ Deng girtî
+ Deng vekirî
+ Ekrana aplîkasyonê biparêze
+ Ji ber xwe ve sûretan qebûl bike
+ Adresa IPyê biparêze
+ Girtî
+ Na
+ Bipirse
+ Ber lînka webê were vekirin?
+ Lînkê veke
+ Lînka timam veke
+ Lînka paqij veke
+ ARÎKARÎ
+ APLÎKASYON
+ DOSYA
+ Ji nû ve veke
+ PROKSIYA SOCKSÊ
+ Sûretên profîlan
+ Girêdana torê
+ Ji kompîterê bişuxulîne
+ Xeletiya databasê
+ Dosya: %s
+ Xeletî: %s
+ Xeletiya nenaskirî
+ dewetiya ji bo koma %1$s
+ Derkeve
+ Kom nehat dîtin!
+ Ev kom nema heye.
+ derket
+ tu derketî
+ %s û %s
+ %s, %s û %d endam
+ adresa ji bo te hat guhertin
+ te adresa ji bo %s guhert
+ te adres guhert
+ mielif
+ endam
+ moderator
+ xwedî
+ redkirî
+ derxistî
+ derketiye
+ nayê zanîn
+ Endam %1$s
+ Rola endamên nû
+ Rola pêşî
+ Ji komê derkeve
+ Lînka komê
+ Xeletî di şandina dewetiyê de
+ Halê dosyayê
+ Wextê standinê
+ Wextê nûkirina qeydiyê: %s
+ Halê dosyayê: %s
+ Wextê şandinê: %s
+ Wextê standinê: %s
+ nivîs nîne
+ Endam derxe?
+ Endaman derxe?
+ Endam derxe
+ Derxe
+ Endam derxe
+ Endam blok bike
+ Blok bike
+ Ji admîn blokkirî
+ blokkirî
+ ne aktîv
+ ENDAM
+ Rol
+ Kom
+ Te standin bi riya
+ Halê torê
+ Girêdanê biedilîne
+ Biedilîne
+ Navê timam î komê:
+ Profîla komê di cihazên endaman de qeydkirî ye, ne di serveran de.
+ Profîla komê qeyd bike
+ Serveran bişuxulîne
+ %s bişuxulîne
+ Li şertan meyzîne
+ Şertên nûkirî
+ %s bişuxulînî, şertên şuxulandinê qebûl bike.]]>
+ Ji bo dosyayan bişuxulîne
+ Serverên medya & dosyayê ên lêzedekirî
+ Şertan veke
+ Guhertinan veke
+ Protokola serverê hat guhertin.
+ Reqema PINGan
+ TCP keep-alive aktîv bike
+ Qeyd bike
+ Qeyd bike û dîse girê de
+ Eyarên torê nû bike?
+ Profîl lê zêde bike
+ Girêdanên profîl û serveran
+ Veşêre
+ Nîşan bide
+ Bêdeng bike
+ Bêdengkirinê betal bike
+ Profîlê bike şexsî!
+ Şîfra profîlê
+ Rehnik
+ Rehnik
+ Reş
+ Sernav
+ Cewaba standî
+ Sûret jê bibe
+ Mezinbûniya fontê
+ Şefafî
+ Êvara te bi xêr!
+ Sibeha te bi xêr!
+ Dagire
+ Endam karin dosya û medya bişînin.
+ Dosya û medya memnû in.
+ Endam karin lînkên SimpleXê bişînin.
+ %d san
+ %d heftî
+ UIa farisî
+ Eyarên nû yên medyayê
+ Aplîkasyonê bi yek destî bişuxulîne.
+ Mezinbûniya fontê zêde bike.
+ Sebeba qutbûna girêdanê: %s
+ Ev lînk bi telefoneke dî re hatiye şuxulandin, bi xêra xwe li kompîterê lînkeke nû çêke.
+ Girêdana bi kompîterê re qut bike?
+ Tenê yek cihaz kare di eynî wextî de bişuxule
+ Li hêviya girêdana telefonê:
+ Ji ber xwe ve girê de
+ %s ne aktîv e]]>
+ %s mijûl e]]>
+ %s re di halekî xirab de ye]]>
+ Siḧbetê veke
+ Xeletî di çêkirina lîsta siḧbetan de
+ Xeletî di vekirina siḧbetê de
+ Siḧbetê bisekinîne
+ Profîlên siḧbetê biguhere
+ Siḧbet
+ Ti siḧbetên te nînin
+ Ti siḧbet di lîsta %s de nînin.
+ Ti siḧbetên nexwendî nînin
+ Siḧbet nînin
+ Ti siḧbet nehatin dîtin
+ Siḧbeta hilbijartî nîne
+ Tiştekî hilbijartî nîne
+ %d hilbijartî
+ Favorît
+ Kom
+ %d siḧbetên bi endaman
+ 1 siḧbeta bi yek endamî
+ %d siḧbet
+ Robot
+ Kom
+ Navê siḧbetê deyne…
+ Siḧbeteke nû bide destpêkirin
+ Ji bo ko yek siḧbete nû bide destpêkirin
+ Siḧbet ber were valakirin?
+ Hemû mesaj wê bên jêbirin - ev nikare were betalkirin/vegerandin! Wê mesaj TENÊ ji bo te bên jêbirin.
+ Siḧbetê vala bike
+ Hemû siḧbet wê ji lîsta %s bên jêbirin, û wê lîste bê jêbirin
+ Siḧbeta nû
+ Profîla sihbetê hilbijêre
+ Profîlên te yên siḧbetê
+ Profîla siḧbetê çêke
+ Ber serverên SimpleX Chatê werin şuxulandin?
+ Profîla siḧbetê
+ Tu siḧbeta xwe qontrol dikî!
+ Siḧbetê bişuxulîne
+ SIḦBET
+ Rengên siḧbetê
+ Siḧbet sekinandî ye
+ DATABASA SIḦBETÊ
+ Ber siḧbet were sekinandin?
+ Xeletî di sekinandina siḧbetê de
+ Ber profîla siḧbetê were jêbirin?
+ ne ti carî
+ Şîfra databasê lazim e ji bo vekirina siḧbetê.
+ Şîfre qeyd bike û siḧbetê veke
+ Siḧbetê veke
+ Siḧbet sekinandî ye
+ Ber siḧbet were destpêkirin?
+ Tu dixwazî ji siḧbetê derkevî?
+ Siḧbetê jê bibe
+ Ber siḧbet were jêbirin?
+ Wê siḧbet ji bo te bê jêbirin - ev nikare were betalkirin/vegerandin!
+ Ji siḧbetê derkeve
+ Tenê xwediyên siḧbetê karin tercihan biguherin.
+ Bi admînan re siḧbetê bike
+ Bi endam re siḧbetê bike
+ Wê endêm ji siḧbetê bê derxistin - ev nikare were betalkirin/vegerandin!
+ Wê endam ji siḧbetê bên derxistin - ev nikare were betalkirin/vegerandin!
+ Siḧbet
+ Wê profîla te yî siḧbetê ji endamên komê re bê şandin
+ Wê profîla te yî siḧbetê ji endamên siḧbetê re bê şandin
+ Serverên ji bo dosyayên nû ên profîla te yî siḧbetê ya niha
+ Ber profîla siḧbetê were jêbirin?
+ Hemû mesaj wê bên jêbirin - ev nikare were betalkirin/vegerandin!
+ Profîla sihbetê jê bibe
+ Profîla siḧbetê hew veşêre
+ Vegerîne temaya aplîkasyonê
+ Vegerîne temaya karber
+ Temaya serî/pêşî diyar bike
+ Moda reḧnik
+ na
+ vekirî
+ girtî`
+ Tercihên siḧbetê
+ Mesajên ko winda dibin li vê siḧbetê nayên qebûlkirin.
+ Jêbirina ko nikare were betalkirin/vegerandin di vê siḧbetê de nayê qebûlkirin.
+ Siḧbetên bi endam
+ Ti siḧbetên bi endam nînin
+ Siḧbetê jê bibe
+ Bi admînan re siḧbetê bike
+ Siḧbetê ji nû ve veke
+ Siḧbet tê sekinandin
+ Siḧbetê bide destpêkirin
+
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml
index 0b59cc1b06..2f26545913 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml
@@ -25,12 +25,12 @@
zmoderowane przez %s
wysyłanie plików nie jest jeszcze obsługiwane
odbieranie plików nie jest jeszcze obsługiwane
- Próbowanie połączenia z serwerem używanym do odbierania wiadomości od tego kontaktu.
+ Próba połączenia z serwerem, który służył do odbierania wiadomości z tego połączenia.
Próbowanie połączenia z serwerem używanym do odbierania wiadomości od tego kontaktu (błąd: %1$s).
nieznany format wiadomości
SimpleX
Ty
- Jesteś połączony z serwerem używanym do odbierania wiadomości od tego kontaktu.
+ Jesteś połączony z serwerem, który służył do odbierania wiadomości z tego połączenia.
Twój profil zostanie wysłany do kontaktu, od którego otrzymałeś ten link.
udostępniłeś jednorazowy link
incognito przez link grupowy
@@ -71,10 +71,10 @@
Błąd usuwania kontaktu
Błąd usuwania grupy
Błąd usuwania oczekującego połączenia kontaku
- Możliwe, że odcisk palca certyfikatu w adresie serwera jest nieprawidłowy
+ Odcisk palca w adresie serwera nie pasuje do certyfikatu.
Bezpieczna kolejka
Nadawca mógł usunąć prośbę o połączenie.
- Serwer wymaga autoryzacji do tworzenia kolejek, sprawdź hasło
+ Serwer wymaga autoryzacji do tworzenia kolejek, sprawdź hasło.
Test nie powiódł się na etapie %s.
Błąd usuwania profilu użytkownika
Błąd aktualizacji prywatności użytkownika
@@ -141,7 +141,7 @@
Usunąć wiadomość członka\?
edytowana
Dla wszystkich
- dołącz jako %s
+ Dołącz jako %s
wysyłanie nie powiodło się
wyślij
Udostępnij plik…
@@ -154,7 +154,7 @@
oznacz jako nieprzeczytane
Witaj!
Witaj %1$s!
- jesteś zaproszony do grupy
+ Jesteś zaproszony do grupy
Nie masz czatów
Czaty
Poproszony o odbiór obrazu
@@ -179,7 +179,7 @@
Oczekiwanie na film
Oczekiwanie na film
jesteś obserwatorem
- Nie możesz wysyłać wiadomości!
+ Jesteś obserwatorem
Połączony
Obecnie maksymalny obsługiwany rozmiar pliku to %1$s.
Usuń kontakt
@@ -428,9 +428,9 @@
Jak to działa
Jak SimpleX działa
Natychmiastowy
- Można to później zmienić w ustawieniach.
+ Jak wpływa na baterię
Nawiąż prywatne połączenie
- dwuwarstwowego szyfrowania end-to-end.]]>
+ Tylko urządzenia klienckie przechowują profile użytkowników, kontakty, grupy i wiadomości.
Okresowo
Prywatne powiadomienia
repozytorium GitHub.]]>
@@ -814,10 +814,10 @@
%d mies
%ds
%d sek
- Członkowie grupy mogą nieodwracalnie usuwać wysłane wiadomości. (24 godziny)
- Członkowie grupy mogą wysyłać bezpośrednie wiadomości.
+ Członkowie mogą nieodwracalnie usuwać wysłane wiadomości. (24 godziny)
+ Członkowie mogą wysyłać bezpośrednie wiadomości.
Nieodwracalne usuwanie wiadomości jest na tym czacie zabronione.
- Nieodwracalne usuwanie wiadomości jest w tej grupie zabronione.
+ Usuwanie wiadomości nieodwracalnych jest zabronione.
Tylko Ty możesz nieodwracalnie usunąć wiadomości (Twój kontakt może oznaczyć je do usunięcia). (24 godziny)
Tylko Ty możesz wysyłać znikające wiadomości.
Tylko Ty możesz wysyłać wiadomości głosowe.
@@ -827,7 +827,7 @@
Zabroń wysyłania bezpośrednich wiadomości do członków.
Zabroń wysyłania znikających wiadomości.
Wiadomości głosowe są zabronione na tym czacie.
- Wiadomości głosowe są zabronione w tej grupie.
+ Wiadomości głosowe są zabronione.
Administratorzy mogą tworzyć linki do dołączania do grup.
Automatyczne akceptowanie próśb o kontakt
anulowano %s
@@ -921,7 +921,7 @@
%d dni
Usuń
Usuń wiadomości po
- Znikające wiadomości są zabronione w tej grupie.
+ Znikające wiadomości są zabronione.
Błąd usuwania prośby o kontakt
Nie znaleziono pliku
Błąd zapisu serwerów SMP
@@ -939,9 +939,9 @@
zaproponował %s: %2s
Tylko właściciele grup mogą włączyć wiadomości głosowe.
Tylko Twój kontakt może nieodwracalnie usunąć wiadomości (możesz oznaczyć je do usunięcia). (24 godziny)
- Hasło nie zostało znalezione w Keystore, wprowadź je ręcznie. Może się tak zdarzyć, gdy przywrócisz dane aplikacji za pomocą narzędzia do kopii zapasowych. Jeśli tak nie jest, skontaktuj się z programistami.
- Członkowie grupy mogą wysyłać znikające wiadomości.
- Członkowie grupy mogą wysyłać wiadomości głosowe.
+ Hasło nie znalezione w Keystore, proszę wpisać je ręcznie. Mogło się to zdarzyć, jeśli przywróciłeś dane aplikacji za pomocą narzędzia do tworzenia kopii zapasowej. Jeśli tak nie jest, skontaktuj się z deweloperami.
+ Członkowie mogą wysyłać znikające wiadomości.
+ Członkowie mogą wysyłać wiadomości głosowe.
Grupa zostanie usunięta dla wszystkich członków - nie można tego cofnąć!
Jak korzystać z Twoich serwerów
zeskanować kod QR w rozmowie wideo, lub Twój rozmówca może udostępnić link z zaproszeniem.]]>
@@ -951,7 +951,7 @@
Zaimportować bazę danych czatu\?
Tryb incognito chroni Twoją prywatność używając nowego losowego profilu dla każdego kontaktu.
pośrednie (%1$s)
- pozwolić SimpleX na działanie tle w następnym oknie dialogowym. W przeciwnym razie powiadomienia zostaną wyłączone.]]>
+ Pozwól w następnym oknie dialogowym natychmiast otrzymywać powiadomienia.]]>
Zainstaluj SimpleX Chat na terminal
Nieprawidłowe potwierdzenie migracji
zaproszenie do grupy %1$s
@@ -985,8 +985,8 @@
Tego działania nie można cofnąć - wszystkie odebrane i wysłane pliki oraz media zostaną usunięte. Obrazy o niskiej rozdzielczości pozostaną.
Adres odbiorczy zostanie zmieniony na inny serwer. Zmiana adresu zostanie zakończona gdy nadawca będzie online.
Ten link nie jest prawidłowym linkiem połączenia!
- SimpleX - zużywa ona kilka procent baterii dziennie.]]>
- Aby chronić prywatność, zamiast identyfikatorów użytkowników używanych przez wszystkie inne platformy, SimpleX ma identyfikatory dla kolejek wiadomości, oddzielne dla każdego z Twoich kontaktów.
+ SimpleX działa w tle zamiast korzystać z powiadomień push.]]>
+ Aby chronić Twoją prywatność, SimpleX używa oddzielnych identyfikatorów dla każdego z Twoich kontaktów.
Aby zweryfikować szyfrowanie end-to-end z Twoim kontaktem porównaj (lub zeskanuj) kod na waszych urządzeniach.
Użyj dla nowych połączeń
O ile Twój kontakt nie usunął połączenia lub ten link był już użyty, może to być błąd - zgłoś go.
@@ -1142,7 +1142,7 @@
Dodaj adres do swojego profilu, aby Twoje kontakty mogły go udostępnić innym osobom. Aktualizacja profilu zostanie wysłana do Twoich kontaktów.
Utwórz adres, aby ludzie mogli się z Tobą połączyć.
Utwórz adres SimpleX
- Zapisz ustawienia automatycznej akceptacji
+ Zapisz ustawienia adresów SimpleX
Udostępnij kontaktom
Możesz go utworzyć później
Adres
@@ -1173,10 +1173,10 @@
Wyślij znikającą wiadomość
Zabroń reakcje wiadomości.
Reakcje wiadomości
- Członkowie grupy mogą dodawać reakcje wiadomości.
+ Członkowie mogą dodawać reakcje na wiadomości.
godziny
Reakcje wiadomości są zabronione na tym czacie.
- Reakcje wiadomości są zabronione w tej grupie.
+ Reakcje na wiadomości są zabronione.
minuty
miesiące
Tylko Ty możesz dodawać reakcje wiadomości.
@@ -1245,9 +1245,9 @@
Pozwól na wysyłanie plików i mediów.
Brak filtrowanych czatów
Nieulubione
- Członkowie grupy mogą wysyłać pliki i media.
+ Członkowie mogą wysyłać pliki i media.
Zakaz wysyłania plików i mediów.
- Pliki i media są zabronione w tej grupie.
+ Pliki i media są zabronione.
Tylko właściciele grup mogą włączać pliki i media.
Szukaj
Wyłączono
@@ -1378,7 +1378,7 @@
Błąd tworzenia kontaktu członka
Wyślij wiadomość bezpośrednią aby połączyć
wyślij wiadomość bezpośrednią
- połącz bezpośrednio
+ Prośba o połączenie
usunięto kontakt
Utwórz grupę
Utwórz profil
@@ -1556,7 +1556,7 @@
członek %1$s zmienił na %2$s
ustaw nowy adres kontaktu
zaktualizowano profil
- Były członek %1$s
+ Członek %1$s
Zablokować członka dla wszystkich?
Utworzony o
Zachowano wiadomość
@@ -1712,7 +1712,7 @@
Wiadomości głosowe są niedozwolone
Włączony dla
właściciele
- Linki SimpleX są zablokowane na tej grupie.
+ Linki SimpleX są zablokowane.
Inne
WiFi
Połączenie ethernet (po kablu)
@@ -1720,7 +1720,7 @@
wszyscy członkowie
Zezwól na wysyłanie linków SimpleX.
Sieć komórkowa
- Członkowie grupy mogą wysyłać linki SimpleX.
+ Członkowie mogą wysyłać linki SimpleX.
Brak połączenia z siecią
Zabroń wysyłania linków SimpleX
Linki SimpleX
@@ -1929,7 +1929,7 @@
Wyświetlanie informacji dla
Statystyki
Sesje transportowe
- Zaczynanie od %s. \nWszystkie dane są prywatne na Twoim urządzeniu.
+ Zaczynanie od %s.\nWszystkie dane są prywatne na Twoim urządzeniu.
Połącz ponownie wszystkie serwery
Połączyć ponownie serwer?
Nie jesteś połączony z tymi serwerami. Prywatne trasowanie jest używane do dostarczania do nich wiadomości.
@@ -2040,7 +2040,7 @@
Zaznacz
Wiadomości zostaną usunięte dla wszystkich członków.
Wiadomości zostaną oznaczone jako moderowane dla wszystkich członków.
- Osiągalny pasek narzędzi czatu
+ Osiągalny pasek narzędzi
Wyeksportowano bazę danych czatu
Kontynuuj
Serwery mediów i plików
@@ -2094,7 +2094,7 @@
Dźwięk wyciszony
Wybierz profil czatu
Udostępnij profil
- Twoje połączenie zostało przeniesione do %s, ale podczas przekierowania do profilu wystąpił nieoczekiwany błąd.
+ Twoje połączenie zostało przeniesione na %s, ale pojawił się błąd podczas zmiany profilu.
Tryb systemu
Przesłane archiwum bazy danych zostanie trwale usunięte z serwerów.
Serwer
@@ -2189,4 +2189,369 @@
Nowy członek chce dołączyć do grupy.
1 rok
Akceptuj
+ rozmowa z członkiem grupy
+ Akceptuj
+ Akceptuj jako członek grupy
+ Akceptuj jako obserwator grupy
+ Akceptuj dodanie kontaktu
+ Akceptuj dodanie kontaktu
+ Akceptuj użytkownika grupy
+ Dodaj wiadomość
+ Wszystko
+ Wszystkie nowe wiadomości od tego użytkownika będą ukryte
+ Zezwól na wszystkie pliki i media tylko jeśli twój kontakt na to pozwala.
+ Zezwól na zgłaszanie raportów do moderatorów.
+ Zezwól twoim kontaktom na wysyłanie plików i mediów.
+ Zezwól na archiwizowanie raportów dla ciebie.
+ Wszystkie serwery
+ Zarchiwizować wszystkie raporty?
+ Zarchiwizować %d raportów?
+ Archiwizuj raporty
+ Lepsze działanie grupy
+ Lepsza prywatność i bezpieczeństwo
+ Opis:
+ Opis zbyt duży
+ Bot
+ Ty i twój kontakt możecie wysyłać pliki i media.
+ Kontakt służbowy
+ Uzywając SimpleX Chat zgadzasz się na:\n- wysyłanie tylko prawnie dopuszczonych treści na publicznych grupach.\n- szanowanie innych użytkowników - nie wysyłanie SPAM-u
+ Nie mogę zmienić profilu
+ nie mogę wysłać wiadomości
+ 4 nowe języki interfejsu
+ Wszystkie wiadomości
+ Blokowanie członków dla wszystkich?
+ Kataloński, indonezyjski, rumuński i wietnamski - dzięki naszym użytkownikom!
+ szyfrowanie end-to-end.]]>
+ tylko po zaakceptowaniu twojego żądania.]]>
+ Zmienić automatyczne usuwania wiadomości?
+ Czaty z członkami
+ Czat z administratorami
+ Czar z administratorami
+ Czat z administratorami
+ Czat z członkiem
+ Czatuj z członkami, zanim dołączą.
+ Konfigurowanie operatorów serwerów
+ Połącz
+ Połącz się szybciej! 🚀
+ kontakt usunięty
+ kontakt zablokowany
+ kontakt nie gotowy
+ PROŚBY O KONTAKT OD GRUP
+ kontakt powinien zaakceptować…
+ Stwórz swój adres
+ %d czat(y)
+ %d czaty z członkami
+ domyślny (%s)
+ Usuń czat
+ Usuń wiadomości czatu z urządzenia.
+ Skasować czat z tym członkiem?
+ Skasuj wiadomości od tego członka
+ Skasować wiadomości od tego członka?
+ Skasuj wiadomości
+ Opcje wycofane
+ Opis jest zbyt duży
+ Bezpośrednie wiadomości między członkami są zabronione.
+ Wiadomości bezpośrednie między członkami są zabronione na tym czacie.
+ Wyłączyć automatyczne usuwanie wiadomości?
+ Zablokuj skasowane wiadomości
+ %d wiadomości
+ Nie przegap ważnych wiadomości.
+ %d raporty
+ Edytuj
+ Włącz domyślne znikanie wiadomości.
+ Włącz Flux w ustawieniach sieci i serwerów, aby uzyskać lepszą prywatność metadanych.
+ Włącz logi
+ Renegocjacja szyfrowania jest w toku.
+ Błąd podczas akceptacji warunków
+ Błąd podczas akceptacji członka
+ Błąd podczas dodawania serwera
+ Błąd podczas zmiany profilu
+ Błąd podczas tworzenia listy czatu
+ Błąd podczas tworzenia raportu
+ Błąd usuwania czatu
+ Błąd ładowania list czatu
+ Błąd oznaczania odczytu
+ Błąd otwierania czatu
+ Błąd otwierania grupy
+ Błąd odczytu bazy danych hasła
+ Błąd odrzucenia prośby o kontakt
+ Błąd zapisywania bazy danych
+ Błąd zapisywania serwerów
+ Błąd zapisywania ustawień
+ Błąd w konfiguracji serwerów.
+ Błąd aktualizowania listy czatu
+ Błąd aktualizacji serwera
+ Szybsze usuwania grup.
+ Szybsze wysyłanie wiadomości.
+ Ulubione
+ Plik jest zablokowany przez operatora serwera:\n%1$s.
+ Pliki
+ Pliki i media są zabronione na tym czacie.
+ Filtr
+ Odcisk palca w docelowym serwerze nie pasuje do certyfikatu: %1$s.
+ Odcisk palca w adresie serwera nie pasuje do certyfikatu: %1$s.
+ Odcisk palca w adresie serwera nie pasuje do certyfikatu: %1$s.
+ Napraw
+ Naprawić połączenie?
+ Dla wszystkich moderatorów
+ Lepsza prywatność metadanych.
+ Dla profilu czatu %s:
+ Na przykład, jeśli kontakt otrzyma wiadomości za pośrednictwem serwera czatu SimpleX, aplikacja dostarczy je za pośrednictwem serwera Flux.
+ Dla mnie
+ Dla prywatnego routingu
+ Dla mediów społecznościowych
+ Pełny link
+ Otrzymaj powiadomienie jeśli ktoś wspomni.
+ Grupa
+ grupa została usunięta
+ Grupy
+ Pomóż administratorom moderować ich grupy.
+ Jak to pomaga prywatności
+ Zdjęcia
+ Poprawiona nawigacja czatu
+ Niewłaściwa zawartość
+ Niewłaściwy profil
+ Zaproszenie do czatu
+ Dołącz do grupy
+ Zachowaj swoje czaty czyste
+ Opuść czat
+ Opuścić czat?
+ Mniejszy ruch w sieciach mobilnych.
+ Linki
+ Lista
+ Lista imion...
+ Nazwa i emoji powinny być inne dla wszystkich list.
+ Wczytywanie profilu…
+ Przyjęcie członkostwa
+ członek posiada starą wersję
+ Członek został usunięty - nie można przyjąć żądania
+ Wiadomości członkowskie zostaną usunięte - nie można tego cofnąć!
+ Raporty członkowskie
+ Członkowie mogą zgłaszać wiadomości moderatorom.
+ Członkowie zostaną usunięci z czatu - tego nie da się cofnąć!
+ Członkowie zostaną usunięci z grupy - nie można tego cofnąć!
+ Członek zostanie usunięty z czatu - nie można tego cofnąć!
+ Członek dołączy do grupy, czy zaakceptować tego członka?
+ Wspomnij członka 👋
+ Wyślij wiadomość natychmiast po dotknięciu Połącz.
+ Wiadomość jest za duża!
+ Wiadomości od tych członków zostaną pokazane!
+ Wiadomości na tym czacie nigdy nie zostaną usunięte.
+ moderator
+ moderatorzy
+ Wycisz wszystko
+ Decentralizacja sieci
+ Operator sieci
+ Operatorzy sieci
+ Nowa rola w grupie: Moderator
+ Nowy serwer
+ Nie
+ Brak usług w tle
+ Żadnych czatów
+ Nie znaleziono żadnych czatów
+ Nie ma czatów na liście %s.
+ Żadnych rozmów z członkami
+ Brak mediów i serwerów plików multimedialnych.
+ Brak wiadomości
+ Brak serwerów wiadomości.
+ Brak prywatnej sesji routingu
+ Brak serwerów prywatnej sesji routingu
+ Brak serwerów do otrzymania plików.
+ Brak serwerów aby otrzymać wiadomości.
+ Brak serwerów do wysyłania plików.
+ brak subskrypcji
+ Notatki
+ Powiadomienia i bateria
+ nie zsynchronizowano
+ Brak nieprzeczytanych czatów
+ wyłączony
+ Wyłącz
+ Tylko właściciele czatu mogą zmieniać preferencje.
+ Widzą to tylko nadawca i moderatorzy
+ Widzisz to tylko Ty i moderatorzy
+ Tylko Ty możesz wysyłać pliki i multimedia.
+ Tylko Twój kontakt może wysyłać pliki i multimedia.
+ Otwórz zmiany
+ Otwórz czat
+ - Otwórz czat w pierwszej nieprzeczytanej wiadomości.\n- Przejdź do cytowanych wiadomości.
+ Otwórz czysty link
+ Otwórz warunki
+ Otwórz pełen link
+ Otwórz link
+ Otwórz linki z listy czatów
+ Otwórz nowy czat
+ Otwórz nową grupę
+ Otwórz by zaakceptować
+ Otwórz aby się połączyć
+ Otwórz aby dołączyć
+ Otwórz aby skorzystać z bota
+ Otworzyć link sieci web?
+ Otwórz z %s
+ Operator
+ Serwer Operatora
+ Organizuj czaty jako listy
+ Lub zaimportuj plik archiwalny
+ Lub udostępnij prywatnie
+ Nie można odczytać hasła w magazynie kluczy. Wprowadź je ręcznie. Mogło się to zdarzyć po aktualizacji systemu niezgodnej z aplikacją. Jeśli tak nie jest, skontaktuj się z programistami.
+ Nie można odczytać hasła w magazynie kluczy. Mogło się to zdarzyć po aktualizacji systemu niezgodnej z aplikacją. Jeśli tak nie jest, skontaktuj się z programistami.
+ oczekuje
+ oczekiwanie zaakceptowane
+ oczekująca recenzja
+ Zmniejsz rozmiar wiadomości i wyślij ją ponownie.
+ Zmniejsz rozmiar wiadomości lub usuń multimedia i wyślij ponownie.
+ Poczekaj, aż moderatorzy grupy rozpatrzą Twoją prośbę o dołączenie do grupy.
+ Domyślne serwery
+ Domyślne serwery
+ Prywatność dla Twoich klientów.
+ Polityka prywatności i warunki korzystania.
+ Prywatne czaty, grupy i Twoje kontakty nie są dostępne dla operatorów serwerów.
+ Nazwy prywatnych plików multimedialnych.
+ Limit czasu routingu prywatnego
+ Zabroń raportowania wiadomości moderatorom.
+ Zabroń wysyłania plików i multimediów.
+ Limit czasu protokołu w tle
+ Dostępny pasek narzędzi czatu
+ Odrzuć
+ Odrzuć prośbę o kontakt
+ odrzucono
+ odrzucono
+ Odrzucić członka?
+ Zdalne telefony komórkowe
+ Usuń i skasuj wiadomości
+ przeniesiono z grupy
+ Usuń śledzenie linków
+ Usunąć członka?
+ Usuwa wiadomości i blokuje członków.
+ Zgłoś
+ Zgłoś treść: zobaczą ją tylko moderatorzy grupy.
+ Na tej grupie zabronione jest zgłaszanie wiadomości.
+ Zgłoś profil członka: będą go widzieć tylko moderatorzy grupy.
+ Zgłoś inne: zobaczą to tylko moderatorzy grupy.
+ Jaki jest powód zgłoszenia?
+ Zgłoś: %s
+ Zgłoszenia
+ Zgłoszenia wysłane do moderatorów
+ Zgłoś spam: tylko moderatorzy grupy będą to widzieć.
+ Zgłoś naruszenie: zobaczą je tylko moderatorzy grupy.
+ Prośba o połączenie od grupy %1$s
+ poproszono o połączenie
+ prośba została wysłana
+ prośba o dołączenie została odrzucona
+ przejrzyj
+ Przejrzyj warunki
+ sprawdzone przez administratorów
+ Przejrzyj członków grupy
+ Przejrzyj później
+ Przejrzyj członków
+ Przejrzyj członków przed przyjęciem (pukanie).
+ Zapisać ustawienia wstępu?
+ Zachowaj listę
+ Poszukaj plików
+ Poszukaj obrazów
+ Poszukaj linków
+ Poszukaj wideo
+ Poszukaj wiadomości głosowych
+ Wybierz operatora sieci
+ Wysłać prośbę o kontakt?
+ Wyślij prywatne zgłoszenia
+ Wyślij prośbę
+ Wyślij prośbę bez wiadomości
+ Wyślij swoją prywatną opinię do grup.
+ Wysłano do Twojego kontaktu po połączeniu.
+ Serwer dodany do operatora %s.
+ Operator serwera zmieniony.
+ Operatorzy serwera
+ Protokół serwera zmieniony.
+ Ustaw nazwę czatu…
+ Ustaw przyjęcie członka
+ Ustaw datę wygaśnięcia wiadomości na czatach.
+ Ustaw biografię profilu i wiadomość powitalną.
+ Udostępnij adres publicznie
+ Udostępnij stary adres
+ Udostępnij stary link
+ Udostępnij adres SimpleX w mediach społecznościowych.
+ Udostępnij swój adres
+ Krótki opis:
+ Krótki link
+ Krótki adres SimpleX
+ Link do kanału na SimpleX
+ SimpleX Chat i Flux zawarły umowę na włączenie do aplikacji serwerów obsługiwanych przez Flux.
+ łącze przekaźnikowe SimpleX
+ Spam
+ Spam
+ %s serwery
+ Dotknij Połącz aby rozpocząć czat
+ Dotknij Połącz, aby wysłać prośbę
+ Dotknij Połącz aby użyć bota
+ Dotknij Stwórz adres SimpleX w menu aby utworzyć go później.
+ Dotknij Dołącz do grupy
+ Przekroczono limit czasu połączenia TCP
+ Port TCP dla wiadomości
+ Adres będzie krótki, a Twój profil zostanie udostępniony za pośrednictwem adresu.
+ Aplikacja chroni Twoją prywatność, korzystając z różnych operatorów w każdej rozmowie.
+ Połączenie osiągnęło limit niedostarczonych wiadomości, Twój kontakt może być offline.
+ Link będzie krótki, a profil grupowy zostanie udostępniony poprzez link.
+ Raport zostanie dla Ciebie zarchiwizowany.
+ Rola zostanie zmieniona na %s. Wszyscy uczestnicy czatu zostaną powiadomieni.
+ Drugi predefiniowany operator w aplikacji!
+ Nadawca NIE zostanie poinformowany.
+ Serwery dla nowych plików Twojego bieżącego profilu czatu
+ Tej akcji nie można cofnąć - wiadomości wysłane i otrzymane na tym czacie wcześniej niż wybrane zostaną usunięte.
+ Ten link wymaga nowszej wersji aplikacji. Zaktualizuj aplikację lub poproś osobę kontaktową o przesłanie kompatybilnego łącza.
+ Ta wiadomość została usunięta lub jeszcze nie otrzymana.
+ To ustawienie jest dla Twojego obecnego profilu.
+ Czas zniknięcia jest ustawiony tylko dla nowych kontaktów.
+ Aby zabezpieczyć się przed wymianą łącza, możesz porównać kody bezpieczeństwa kontaktu.
+ Żeby odebrać
+ Żeby wysłać
+ Aby wysyłać polecenia, musisz być podłączony.
+ Aby po próbie połączenia skorzystać z innego profilu, usuń czat i użyj linku ponownie.
+ Przeźroczystość
+ Odblokować członków dla wszystkich?
+ Niedostarczone wiadomości
+ Nieprzeczytane wzmianki
+ Nieobsługiwane łącze połączenia
+ Aktualizacja
+ Warunki aktualizacji
+ Aktualizuj swój adres
+ Upgrade
+ Uaktualnij adres
+ Uaktualnić adres?
+ Uaktualnij link do grupy
+ Uaktualnić link do grupy?
+ Użyj dla plików
+ Użyj dla wiadomości
+ Użyj profilu incognito
+ Użyj %s
+ Użyj serwerów
+ Użyj portu TCP %1$s, jeśli nie określono żadnego portu.
+ Używaj portu TCP 443 tylko dla wstępnie ustawionych serwerów.
+ Użyj portu internetowego
+ Wideo
+ Zobacz warunki
+ Zobacz zaktualizowane warunki
+ Wiadomości głosowe
+ Strona Internetowa
+ Wiadomość powitalna
+ Powitaj swoje kontakty
+ Gdy włączony jest więcej niż jeden operator, żaden z nich nie ma metadanych pozwalających dowiedzieć się, kto się z kim komunikuje.
+ Tak
+ zaakceptowałeś tego członka
+ Nie masz połączenia z serwerem używanym do odbierania wiadomości z tego połączenia (brak subskrypcji).
+ Możesz skonfigurować operatorów w ustawieniach sieci i serwerów.
+ Serwery można skonfigurować w ustawieniach.
+ Możesz skopiować i zmniejszyć rozmiar wiadomości, aby ją wysłać.
+ Możesz wzmiankować do %1$s członków na wiadomość!
+ Możesz ustawić nazwę połączenia, aby zapamiętać, z kim link został udostępniony.
+ Nie możesz wysyłać wiadomości!
+ Możesz przeglądać swoje raporty na czacie z administratorami.
+ odszedłeś
+ Twój opis:
+ Twój kontakt biznesowy
+ Twój profil na czacie zostanie wysłany do członków czatu
+ Twój kontakt
+ Twoja grupa
+ Twój profil
+ Twoje serwery
+ Przestaniesz otrzymywać wiadomości z tego czatu. Historia czatu zostanie zachowana.
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml
index 95e5ed9409..5445c57055 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml
@@ -340,7 +340,7 @@
Одноразовая ссылка
Настройки
- Ваш SimpleX адрес
+ Ваш адрес SimpleX
База данных
Подробнее о SimpleX Chat
Как использовать
@@ -428,7 +428,7 @@
Ваш профиль, контакты и доставленные сообщения хранятся на Вашем устройстве.
Профиль отправляется только Вашим контактам.
Имя профиля не может содержать пробелы.
- Введите ваше имя:
+ Имя:
Создать
О SimpleX
@@ -496,7 +496,7 @@
видеозвонок
аудиозвонок
- Аудио- и видеозвонки
+ Аудио и видеозвонки
Ваши звонки
Всегда соединяться через relay
Звонки на экране блокировки:
@@ -627,7 +627,7 @@
Текущий пароль…
Новый пароль…
Подтвердите новый пароль…
- Поменять пароль
+ Сменить пароль
Пожалуйста, введите правильный пароль.
База данных НЕ зашифрована. Установите пароль, чтобы защитить Ваши данные.
Android Keystore используется для безопасного хранения пароля - это позволяет стабильно получать уведомления в фоновом режиме.
@@ -636,7 +636,7 @@
Пароль базы данных будет безопасно сохранён в Android Keystore после запуска чата или изменения пароля - это позволит стабильно получать уведомления.
Пароль не сохранён на устройстве — Вы будете должны ввести его при каждом запуске чата.
Зашифровать базу данных?
- Поменять пароль базы данных?
+ Сменить пароль базы данных?
База данных будет зашифрована.
База данных будет зашифрована и пароль сохранён в Keystore.
Пароль базы данных будет изменён и сохранён в Keystore.
@@ -660,7 +660,7 @@
Введите пароль…
Сохранить пароль и открыть чат
Открыть чат
- Попытка поменять пароль базы данных не была завершена.
+ Попытка изменить пароль базы данных не была завершена.
Восстановить резервную копию
Восстановить резервную копию?
Введите предыдущий пароль после восстановления резервной копии. Это действие нельзя отменить.
@@ -713,7 +713,7 @@
Вы поменяли роль себе на: %s
Вы удалили %1$s
Вы покинули группу
- профиль группы обновлен
+ профиль группы обновлён
поменял(а) адрес для Вас
смена адреса…
@@ -818,7 +818,7 @@
Включить TCP keep-alive
Сохранить
Обновить настройки сети?
- Обновление настроек приведет к переподключению клиента ко всем серверам.
+ Обновление настроек приведёт к переподключению клиента ко всем серверам.
Обновить
Инкогнито
@@ -880,7 +880,7 @@
Прямые сообщения между членами группы запрещены.
Члены могут необратимо удалять отправленные сообщения. (24 часа)
Необратимое удаление сообщений запрещено.
- Члены могут отправлять голосовые сообщения.
+ Участники могут отправлять голосовые сообщения.
Голосовые сообщения запрещены.
Минимальный расход батареи. Вы получите уведомления только когда приложение запущено, без фонового сервиса.]]>
Уведомления
@@ -981,7 +981,7 @@
Только локальные данные профиля
Сообщения
Серверы для новых соединений Вашего текущего профиля чата
- Ваши профили
+ Профили
Все чаты и сообщения будут удалены - это нельзя отменить!
Сборка приложения: %s
Версия приложения: v%s
@@ -1059,7 +1059,7 @@
Установите приветственное сообщение для новых членов группы.
Нажмите на профиль, чтобы переключиться на него.
Благодаря пользователям - добавьте переводы через Weblate!
- Вы все равно получите звонки и уведомления в профилях без звука, когда они активные.
+ Вы всё равно получите звонки и уведомления в профилях без звука, когда они активные.
Вы можете скрыть или отключить уведомления профиля - нажмите и удерживайте профиль, чтобы открыть меню.
Изображение будет принято когда Ваш контакт его загрузит.
Файл будет принят когда Ваш контакт загрузит его.
@@ -1074,7 +1074,7 @@
версия базы данных новее чем приложения, но нет миграции для отката: %s
разная миграция в приложении/базе данных: %s / %s
Откатить версию и открыть чат
- Предупреждение: Вы можете потерять какие то данные!
+ Предупреждение: Вы можете потерять некоторые данные!
ID базы данных и опция Отдельные транспортные сессии.
Показать опции для разработчиков
Удалить профиль чата
@@ -1203,7 +1203,7 @@
Изменить код самоуничтожения
Самоуничтожение
Код самоуничтожения включен!
- Код доступа в приложение будет заменен кодом самоуничтожения.
+ Код доступа в приложение будет заменён кодом самоуничтожения.
Включить код самоуничтожения
Код самоуничтожения
Код самоуничтожения изменен!
@@ -1243,13 +1243,13 @@
Ваши контакты сохранятся.
Настроить тему
Создайте адрес, чтобы можно было соединиться с Вами.
- Все Ваши контакты сохранятся. Обновленный профиль будет отправлен Вашим контактам.
+ Все Ваши контакты сохранятся. Обновлённый профиль будет отправлен Вашим контактам.
Добавьте адрес в свой профиль, чтобы Ваши контакты могли поделиться им. Профиль будет отправлен Вашим контактам.
Создать адрес SimpleX
Поделиться с контактами
Прекратить делиться адресом\?
Автоприём
- Введите приветственное сообщение... (опционально)
+ Введите приветственное сообщение... (по желанию)
Сохранить настройки\?
Прекратить делиться
Продолжить
@@ -1373,7 +1373,7 @@
Пересогласовать шифрование
Быстрый поиск чатов
Отчёты о доставке сообщений!
- Еще несколько изменений
+ Ещё несколько изменений
Отчёты о доставке!
Включить
Даже когда они выключены в разговоре.
@@ -1622,7 +1622,7 @@
Ошибка создания сообщения
Ошибка удаления заметки
Венгерский и Турецкий интерфейс
- Искать или вставьте ссылку SimpleX
+ Поиск или вставить ссылку SimpleX
Этот QR-код не является SimpleX-ccылкой.
С зашифрованными файлами и медиа.
С уменьшенным потреблением батареи.
@@ -1682,15 +1682,15 @@
Сохранённое сообщение
неизвестно
неизвестный статус
- %d сообщений заблокировано администратором
+ %d сообщений заблокировано админом
%s заблокирован
%s разблокирован
Вы разблокировали %s
Разблокировать для всех
Заблокировать члена группы для всех?
заблокирован
- заблокировано администратором
- Заблокирован администратором
+ заблокировано админом
+ Заблокирован админом
Заблокировать для всех
Ошибка при блокировании члена группы для всех
Разблокировать члена группы для всех?
@@ -1750,7 +1750,7 @@
Завершить миграцию
Или передайте эту ссылку
Миграция завершена
- Внимание: запуск чата на нескольких устройствах не поддерживается и приведет к сбоям доставки сообщений.
+ Внимание: запуск чата на нескольких устройствах не поддерживается и приведёт к сбоям доставки сообщений.
не должны использовать одну и ту же базу данных на двух устройствах.]]>
Проверьте подключение к Интернету и повторите попытку
Подтвердите, что Вы помните пароль базы данных для её миграции.
@@ -1815,7 +1815,7 @@
Ссылки SimpleX
Разрешить отправлять ссылки SimpleX.
Запретить отправку ссылок SimpleX
- Члены могут отправлять SimpleX ссылки.
+ Участники могут отправлять ссылки SimpleX.
админы
все члены
владельцы
@@ -1836,9 +1836,9 @@
ФАЙЛЫ
Новые темы чатов
нет
- Светлая
- Системная
- Цвета тёмного режима
+ Светлый
+ Системный
+ Цвета темного режима
Получайте файлы безопасно
Конфиденциальная доставка 🚀
Улучшенная доставка сообщений
@@ -1871,7 +1871,7 @@
Всегда
Подтверждать файлы с неизвестных серверов.
Всегда использовать конфиденциальную доставку.
- Тёмная
+ Тёмный
Отладка доставки
Ошибка инициализации WebView. Обновите Вашу систему до новой версии. Свяжитесь с разработчиками.
\nОшибка: %s
@@ -1948,7 +1948,7 @@
Неверный ключ или неизвестный адрес блока файла - скорее всего, файл удален.
Выбранные настройки чата запрещают это сообщение.
Ошибка файла
- Сканировать / Вставить ссылку
+ Сканировать QR-код/ Вставить ссылку
Другие XFTP-серверы
Настроенные XFTP-серверы
Загрузка %s (%s)
@@ -2016,7 +2016,7 @@
Слабое
Среднее
Выключено
- Доступная панель приложения
+ Панель приложения внизу
Текущий профиль
Нет информации, попробуйте перезагрузить
Информация о серверах
@@ -2217,7 +2217,7 @@
%s.]]>
%s.]]>
%s, примите условия использования.]]>
- Для оправки
+ Для отправки
Дополнительные серверы сообщений
Использовать для файлов
Открыть условия
@@ -2341,11 +2341,11 @@
Ошибка обновления списка чата
Ошибка создания списка чатов
Список
- Никаких чатов в списке %s.
+ Нет чатов в списке %s.
Без непрочитанных чатов
Никаких чатов
Чаты не найдены
- Все чаты будут удалены из списка %s, а сам список удален
+ Все чаты будут удалены из списка %s, а сам список удалён
Добавить список
Примечания
Открыть в %s
@@ -2380,7 +2380,7 @@
Улучшенная приватность и безопасность
Ускорено удаление групп.
Ускорена отправка сообщений.
- Помогайте администраторам модерировать их группы.
+ Помогайте админам модерировать их группы.
Организуйте чаты в списки
Вы можете сообщить о нарушениях
Установите время исчезания сообщений в чатах.
@@ -2426,7 +2426,7 @@
модератор
ожидает утверждения
ожидает
- Обновленные условия
+ Обновлённые условия
Запретить жаловаться модераторам группы.
Члены группы могут пожаловаться модераторам.
Сообщения в этом чате никогда не будут удалены.
@@ -2566,7 +2566,7 @@
Член группы удалён - невозможно принять запрос
Чтобы использовать другой профиль после попытки соединения, удалите чат и используйте ссылку снова.
Приветственное сообщение
- О Вас:
+ О себе:
Ваш профиль
Описание слишком длинное
Использовать профиль инкогнито
@@ -2615,4 +2615,23 @@
Хэш в адресе сервера не соответствует сертификату: %1$s.
Ссылка SimpleX relay
Хэш в адресе сервера назначения не соответствует сертификату: %1$s.
+ Удалить сообщения участника
+ Удалить сообщения участника?
+ Удалить сообщения
+ Сообщения участника будут удалены - это действие не обратимо!
+ нет подписки
+ Вы не подключенны к серверу через который Вы получали сообщения от этого контакта (без подписки).
+ Удалить члена группы и удалить сообщения
+ Все сообщения
+ Файлы
+ Фильтр
+ Изображения
+ Ссылки
+ Поиск файлов
+ Поиск изображений
+ Поиск ссылок
+ Поиск видео
+ Поиск голосовых сообщений
+ Видео
+ Голосовые сообщения
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/sv/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/sv/strings.xml
new file mode 100644
index 0000000000..55344e5192
--- /dev/null
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/sv/strings.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml
index 501f07ea50..16d821637b 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml
@@ -836,7 +836,7 @@
Mesaj tepkileri
Tercihleriniz
Mesaj tepkileri yasaklıdır.
- Bu kişiden mesaj almak için kullanılan sunucuya bağlısınız.
+ Bu bağlantıdan gelen mesajları almak için kullanılan sunucuya bağlısınız.
Zaten %1$s e bağlısınız
Doğrulanamadınız; lütfen tekrar deneyin.
SimpleX Kilidini Ayarlar üzerinden açabilirsiniz.
@@ -938,7 +938,7 @@
kapalı
açık
Kilit modunu değiştir
- Bu kişiden mesaj almak için kullanılan sunucuya bağlanılmaya çalışılıyor.
+ Bu bağlantıdan gelen mesajları almak için kullanılan sunucuya bağlanmayı dene.
Lütfen doğru bağlantıyı kullandığınızı kontrol edin veya irtibat kişinizden size başka bir bağlantı göndermesini isteyin.
SimpleX arka planda çalışır.]]>
Periyodik bildirimler
@@ -2509,4 +2509,7 @@
Komutlar gönderebilmek için bağlanmanış olmanız gereklidir.
Üye silinmiş - isteği kabul edemeyecek
Grup linkini güncelle
+ Abonelik yok
+ Bu bağlantıdan mesaj almak için kullanılan sunucuya bağlı değilsiniz (abonelik yok).
+ SimpleX Relay Linki
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml
index 5f729d4ea3..3e5e09c039 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml
@@ -2525,4 +2525,21 @@
服务器地址证书和证书不匹配:%1$s。
无订阅
未连接到用于从该连接接收消息的服务器(无订阅)。
+ 删除成员消息
+ 删除成员消息吗?
+ 删除消息
+ 成员消息将被删除 - 这无法撤销!
+ 移除并删除消息
+ 所有消息
+ 文件
+ 筛选器
+ 图片
+ 链接
+ 搜索文件
+ 搜索图片
+ 搜索链接
+ 搜索视频
+ 搜索语音消息
+ 视频
+ 语音消息
diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt
index d0ba082adf..3a93df406d 100644
--- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt
+++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt
@@ -21,13 +21,20 @@ import kotlin.math.sqrt
private fun errorBitmap(): ImageBitmap =
ImageIO.read(ByteArrayInputStream(Base64.getMimeDecoder().decode("iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAKVJREFUeF7t1kENACEUQ0FQhnVQ9lfGO+xggITQdvbMzArPey+8fa3tAfwAEdABZQspQStgBssEcgAIkSAJkiAJljtEgiRIgmUCSZAESZAESZAEyx0iQRIkwTKBJEiCv5fgvTd1wDmn7QAP4AeIgA4oW0gJWgEzWCZwbQ7gAA7ggLKFOIADOKBMIAeAEAmSIAmSYLlDJEiCJFgmkARJkARJ8N8S/ADTZUewBvnTOQAAAABJRU5ErkJggg=="))).toComposeImageBitmap()
+private val base64BitmapCache = Collections.synchronizedMap(object : LinkedHashMap(200, 0.75f, true) {
+ override fun removeEldestEntry(eldest: Map.Entry): Boolean = size > 200
+})
+
actual fun base64ToBitmap(base64ImageString: String): ImageBitmap {
+ base64BitmapCache[base64ImageString]?.let { return it }
val imageString = base64ImageString
.removePrefix("data:image/png;base64,")
.removePrefix("data:image/jpg;base64,")
return try {
- ImageIO.read(ByteArrayInputStream(Base64.getMimeDecoder().decode(imageString))).toComposeImageBitmap()
- } catch (e: IOException) {
+ ImageIO.read(ByteArrayInputStream(Base64.getMimeDecoder().decode(imageString))).toComposeImageBitmap().also {
+ base64BitmapCache[base64ImageString] = it
+ }
+ } catch (e: Throwable) {
Log.e(TAG, "base64ToBitmap error: $e")
errorBitmap()
}
diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt
index 9be10a584b..ed2f6e7859 100644
--- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt
+++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt
@@ -23,6 +23,7 @@ private const val SERVER_HOST = "localhost"
private const val SERVER_PORT = 50395
val connections = ArrayList()
+// Spec: spec/services/calls.md#ActiveCallView
@Composable
actual fun ActiveCallView() {
val scope = rememberCoroutineScope()
diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.desktop.kt
index 38054cb873..b4a24e3572 100644
--- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.desktop.kt
+++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.desktop.kt
@@ -2,6 +2,7 @@ package chat.simplex.common.views.chat.item
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.*
+import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.painter.Painter
import chat.simplex.common.model.CIFile
import chat.simplex.common.platform.*
@@ -17,7 +18,7 @@ actual fun SimpleAndAnimatedImageView(
ImageView: @Composable (painter: Painter, onClick: () -> Unit) -> Unit
) {
// LALAL make it animated too
- ImageView(imageBitmap.toAwtImage().toPainter()) {
+ ImageView(BitmapPainter(imageBitmap)) {
if (getLoadedFilePath(file) != null) {
ModalManager.fullscreen.showCustomModal(animated = false) { close ->
ImageFullScreenView(imageProvider, close)
diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt
index d541a5780e..8d69607c62 100644
--- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt
+++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt
@@ -16,6 +16,7 @@ import kotlinx.coroutines.delay
import java.io.ByteArrayInputStream
import java.io.File
import java.net.URI
+import java.util.*
import javax.imageio.ImageIO
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
@@ -128,6 +129,14 @@ actual fun getAppFileUri(fileName: String): URI {
}
}
+private val loadedImageCache = Collections.synchronizedMap(object : LinkedHashMap>(30, 0.75f, true) {
+ override fun removeEldestEntry(eldest: Map.Entry>): Boolean = size > 30
+})
+
+actual fun clearImageCaches() {
+ loadedImageCache.clear()
+}
+
actual suspend fun getLoadedImage(file: CIFile?): Pair? {
var filePath = getLoadedFilePath(file)
if (chatModel.connectedToRemote() && filePath == null) {
@@ -135,10 +144,10 @@ actual suspend fun getLoadedImage(file: CIFile?): Pair?
filePath = getLoadedFilePath(file)
}
return if (filePath != null) {
- try {
+ loadedImageCache[filePath] ?: try {
val data = if (file?.fileSource?.cryptoArgs != null) readCryptoFile(filePath, file.fileSource.cryptoArgs) else File(filePath).readBytes()
val bitmap = getBitmapFromByteArray(data, false)
- if (bitmap != null) bitmap to data else null
+ if (bitmap != null) (bitmap to data).also { loadedImageCache[filePath] = it } else null
} catch (e: Exception) {
Log.e(TAG, "Unable to read crypto file: " + e.stackTraceToString())
null
diff --git a/apps/multiplatform/desktop/build.gradle.kts b/apps/multiplatform/desktop/build.gradle.kts
index 60ff535e88..1e7bda37c4 100644
--- a/apps/multiplatform/desktop/build.gradle.kts
+++ b/apps/multiplatform/desktop/build.gradle.kts
@@ -40,6 +40,7 @@ compose {
}
mainClass = "chat.simplex.desktop.MainKt"
nativeDistributions {
+ copyright = "(c) 2020-2026 SimpleX Chat"
// For debugging via VisualVM
if (debugJava) {
modules("jdk.zipfs", "jdk.unsupported", "jdk.management.agent")
diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties
index 38ef04daaa..1354ce0cf3 100644
--- a/apps/multiplatform/gradle.properties
+++ b/apps/multiplatform/gradle.properties
@@ -24,13 +24,13 @@ android.nonTransitiveRClass=true
kotlin.mpp.androidSourceSetLayoutVersion=2
kotlin.jvm.target=11
-android.version_name=6.5-beta.3
-android.version_code=331
+android.version_name=6.5-beta.5
+android.version_code=335
android.bundle=false
-desktop.version_name=6.5-beta.3
-desktop.version_code=128
+desktop.version_name=6.5-beta.5
+desktop.version_code=131
kotlin.version=2.1.20
gradle.plugin.version=8.7.0
diff --git a/apps/multiplatform/product/README.md b/apps/multiplatform/product/README.md
new file mode 100644
index 0000000000..173def8ae7
--- /dev/null
+++ b/apps/multiplatform/product/README.md
@@ -0,0 +1,396 @@
+# SimpleX Chat Android & Desktop -- Product Overview
+
+> SimpleX Chat multiplatform product specification (Android + Desktop). Bidirectional code links: product docs reference source files, source files reference product docs.
+>
+> **Related spec:** [spec/README.md](../spec/README.md) | [spec/architecture.md](../spec/architecture.md)
+
+## Table of Contents
+
+1. [Executive Summary](#executive-summary)
+2. [Vision](#vision)
+3. [Target Users](#target-users)
+4. [Capability Map](#capability-map)
+5. [Navigation Map](#navigation-map)
+6. [Related Specifications](#related-specifications)
+
+## Executive Summary
+
+SimpleX Chat is the first messaging platform with no user identifiers of any kind -- not even random numbers. It provides end-to-end encrypted messaging (with optional post-quantum cryptography), audio/video calls, file sharing, and group communication through a fully decentralized architecture where users control their own SMP relay servers.
+
+The Android and Desktop apps share a single **Kotlin Multiplatform + Compose Multiplatform** codebase. Common UI and business logic lives in a shared `common/` module, while platform-specific behavior (notifications, audio, video playback, file system access, call management) is abstracted through the Kotlin `expect`/`actual` pattern and a runtime `PlatformInterface` delegate. The Haskell core library is loaded via **JNI** (`external fun` declarations in `Core.kt`), exposing the full SimpleX Chat API (message send/receive, encryption, migration, file handling) through native FFI.
+
+Key platform differences:
+
+- **Android** uses a 2-column layout (`AndroidScreen`): chat list slides to chat view. Background messaging is handled by `SimplexService` (foreground service) + `MessagesFetcherWorker` (WorkManager periodic fetch). Calls use a dedicated `CallService` + `CallActivity`.
+- **Desktop** uses a 3-column layout (`DesktopScreen`): chat list (start) | chat view (center) | detail panel (`ModalManager.end`). It includes `AppUpdater` for in-app update checking, `StoreWindowState` for window geometry persistence, and VLC-based video playback. Calls use browser-based WebRTC rendered inline.
+
+---
+
+## Vision
+
+SimpleX Chat is the first messaging platform that has no user identifiers -- not even random numbers. It uses double-ratchet end-to-end encryption with optional post-quantum cryptography. The system is fully decentralized with user-controlled SMP relay servers.
+
+The protocol design ensures that no server or network observer can determine who communicates with whom. Each conversation uses separate unidirectional messaging queues on potentially different servers, and there is no shared identifier between the sender and receiver queues.
+
+---
+
+## Target Users
+
+- **Privacy-conscious individuals** wanting secure messaging without phone-number or email-based identity
+- **Groups and communities** needing encrypted group communication with role-based access control
+- **Users avoiding identity linkage** who want to communicate without any persistent user identifier
+- **Organizations** needing self-hosted messaging infrastructure with full control over relay servers
+- **Desktop users** wanting a native desktop client with the same privacy guarantees as the mobile app
+
+---
+
+## Capability Map
+
+All source paths below are relative to `apps/multiplatform/`. The common source root is `common/src/commonMain/kotlin/chat/simplex/common/`.
+
+### 1. Messaging
+
+Core message composition, delivery, and interaction features.
+
+| Feature | Description | Key Source (Kotlin) |
+|---------|-------------|---------------------|
+| Text with markdown | Rich text formatting with SimpleX markdown syntax | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt` |
+| Images | Compressed inline images with full-screen gallery | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt` |
+| Video | Video message recording and playback | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVideoView.kt` |
+| Voice messages | Audio recording and playback (5min / 510KB limit) | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVoiceView.kt` |
+| File sharing | Files up to 1GB via XFTP protocol | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt` |
+| Link previews | OpenGraph metadata extraction and display | `common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LinkPreviews.kt` |
+| Message reactions | Emoji reactions on sent/received messages | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/EmojiItemView.kt` |
+| Message editing | Edit previously sent messages | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt` |
+| Message deletion | Broadcast delete (for recipient) or internal-only delete | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/MarkedDeletedItemView.kt` |
+| Timed messages | Self-destructing messages with configurable TTL | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIChatFeatureView.kt` |
+| Quoted replies | Reply to specific messages with quote context | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContextItemView.kt` |
+| Forwarding | Forward messages between chats | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt` |
+| Search | Full-text search within conversations | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt` |
+| Message reports | Report messages to group moderators | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupReportsView.kt` |
+| Send message bar | Composable message input with attachments, voice, send button | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt` |
+
+### 2. Contacts
+
+Establishing, managing, and verifying contacts.
+
+| Feature | Description | Key Source (Kotlin) |
+|---------|-------------|---------------------|
+| Add via SimpleX address | Connect using a SimpleX contact address | `common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt` |
+| Add via QR code | Scan QR code to establish connection | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/ScanCodeView.kt` |
+| Contact requests | Accept or reject incoming contact requests | `common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ContactRequestView.kt` |
+| Local aliases | Set private display names for contacts | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt` |
+| Contact verification | Compare security codes out-of-band | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt` |
+| Blocking | Block contacts from sending messages | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt` |
+| Incognito mode | Per-contact random profile generation | `common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/IncognitoView.kt` |
+| Bot detection | Identify automated/bot contacts | `common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt` |
+| Contact list | Dedicated contact browsing view | `common/src/commonMain/kotlin/chat/simplex/common/views/contacts/ContactListNavView.kt` |
+
+### 3. Groups
+
+Multi-party encrypted conversations with role-based management.
+
+| Feature | Description | Key Source (Kotlin) |
+|---------|-------------|---------------------|
+| Create groups | Create new group with initial members | `common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt` |
+| Invite members | Invite by individual contact or link | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt` |
+| Member roles | Owner, admin, moderator, member, observer | `common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt` |
+| Member admission | Queue-based admission with review workflow | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberAdmission.kt` |
+| Group links | Shareable invite links for groups | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt` |
+| Business chat mode | Structured business communication groups | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt` |
+| Content moderation | Member reports and moderator actions | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberSupportView.kt` |
+| Group preferences | Configure group-level feature settings | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt` |
+| Member direct contacts | Establish direct chats from group membership | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt` |
+| Group mentions | @-mention members in group messages | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMentions.kt` |
+| Welcome message | Custom welcome message for new group members | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt` |
+| Group profile | Edit group name, image, description | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt` |
+| Member support chat | Scoped support threads between members and admins | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberSupportChatView.kt` |
+
+### 4. Calling
+
+End-to-end encrypted audio and video communication.
+
+| Feature | Description | Key Source (Kotlin) |
+|---------|-------------|---------------------|
+| E2E encrypted calls | Audio/video calls via WebRTC with E2E encryption | `common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt` |
+| Call manager | Call state machine and lifecycle management | `common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt` |
+| Call history | Call events displayed as chat items | `common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CICallItemView.kt` |
+| Incoming call view | Dedicated UI for incoming call notifications | `common/src/commonMain/kotlin/chat/simplex/common/views/call/IncomingCallAlertView.kt` |
+| Android CallService | Foreground service for active calls on Android | `android/src/main/java/chat/simplex/app/CallService.kt` |
+| Android CallActivity | Dedicated Activity for call UI on Android | `android/src/main/java/chat/simplex/app/views/call/CallActivity.kt` |
+| Desktop inline calls | Browser-based WebRTC rendered inline in desktop window | `common/src/commonMain/kotlin/chat/simplex/common/views/call/CallView.kt` |
+
+### 5. Privacy & Security
+
+Encryption, authentication, and privacy controls.
+
+| Feature | Description | Key Source (Kotlin) |
+|---------|-------------|---------------------|
+| E2E encryption | Double-ratchet encryption for all messages | `common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt` |
+| Post-quantum encryption | Optional PQ key exchange for direct chats | `common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt` |
+| Local authentication | Biometric (fingerprint/face) or app passcode lock | `common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt` |
+| Passcode entry | Custom numeric/alphanumeric passcode UI | `common/src/commonMain/kotlin/chat/simplex/common/views/localauth/PasscodeView.kt` |
+| Hidden profiles | Password-protected profiles invisible in UI | `common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt` |
+| Database encryption | AES encryption of local SQLite database | `common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt` |
+| Screen privacy | Blur/hide app content when in app switcher | `common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt` |
+| Encrypted file storage | Local files encrypted at rest | `common/src/commonMain/kotlin/chat/simplex/common/model/CryptoFile.kt` |
+| Delivery receipts control | Toggle delivery/read receipts per contact/group | `common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt` |
+| App lock | Automatic lock on background/timeout with configurable delay | `common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt` |
+
+### 6. User Management
+
+Multiple profiles and identity management.
+
+| Feature | Description | Key Source (Kotlin) |
+|---------|-------------|---------------------|
+| Multiple profiles | Multiple user profiles within one app | `common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt` |
+| Active user switching | Switch between profiles via user picker | `common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt` |
+| Incognito contacts | Per-contact random identities | `common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/IncognitoView.kt` |
+| Profile sharing | Share profile via contact address link | `common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt` |
+| User muting | Mute notifications for specific profiles | `common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt` |
+| User profile editing | Edit display name and profile image | `common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt` |
+
+### 7. Network
+
+Server configuration, proxy support, and connectivity.
+
+| Feature | Description | Key Source (Kotlin) |
+|---------|-------------|---------------------|
+| Custom SMP servers | Configure personal SMP relay servers | `common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServersView.kt` |
+| Custom XFTP servers | Configure personal XFTP file servers | `common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServersView.kt` |
+| Tor/onion support | Route traffic through Tor .onion addresses | `common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/AdvancedNetworkSettings.kt` |
+| SOCKS5 proxy | Route connections through SOCKS5 proxy | `common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/AdvancedNetworkSettings.kt` |
+| Custom ICE servers | Configure WebRTC ICE/TURN servers | `common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/RTCServers.kt` |
+| Network timeouts | Configure connection timeout parameters | `common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/AdvancedNetworkSettings.kt` |
+| Server operators | Configure and manage SMP/XFTP server operators | `common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt` |
+| Server status | View aggregate server connectivity status | `common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt` |
+| Network & servers hub | Central network configuration entry point | `common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt` |
+
+### 8. Customization
+
+Visual appearance and UI preferences.
+
+| Feature | Description | Key Source (Kotlin) |
+|---------|-------------|---------------------|
+| Themes | Light, dark, SimpleX, black, and custom themes | `common/src/commonMain/kotlin/chat/simplex/common/ui/theme/ThemeManager.kt` |
+| Wallpapers | Preset and custom chat wallpapers | `common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatWallpaper.kt` |
+| Chat bubble styling | Customize message bubble appearance | `common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt` |
+| One-handed UI mode | Compact layout for single-hand use (Android) | `common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt` |
+| Language selection | In-app language override | `common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt` |
+| Theme mode editor | Interactive theme color and mode customization | `common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ThemeModeEditor.kt` |
+
+### 9. Data Management
+
+Import, export, encryption, and storage management.
+
+| Feature | Description | Key Source (Kotlin) |
+|---------|-------------|---------------------|
+| Export/import profiles | Full database export and import | `common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt` |
+| Database encryption | Encrypt/decrypt local database with passphrase | `common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt` |
+| Local file encryption | Encrypt stored media and attachments | `common/src/commonMain/kotlin/chat/simplex/common/model/CryptoFile.kt` |
+| Database error handling | Recovery UI for database migration failures | `common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt` |
+| Device-to-device migration | Migrate full profile between devices | `common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt` |
+| Receive migration | Accept incoming device migration transfer | `common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt` |
+| Database utilities | Key storage, password management, helper functions | `common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt` |
+
+### 10. Desktop Features
+
+Desktop-specific functionality not present on Android.
+
+| Feature | Description | Key Source (Kotlin) |
+|---------|-------------|---------------------|
+| 3-column layout | Start (chat list) / center (chat) / end (detail) panels | `common/src/commonMain/kotlin/chat/simplex/common/App.kt` (`DesktopScreen`) |
+| ModalManager.end | Third-column detail panel for settings/info views | `common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt` (`ModalManager`) |
+| App update checker | In-app notification for available updates | `common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/AppUpdater.kt` |
+| Window state persistence | Save/restore window position and dimensions | `common/src/desktopMain/kotlin/chat/simplex/common/StoreWindowState.kt` |
+| VLC video playback | Desktop video playback via VLC native libraries | `common/src/desktopMain/kotlin/chat/simplex/common/platform/VideoPlayer.desktop.kt` |
+| Desktop app entry | Main function, Haskell init, VLC loading | `desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt` |
+| Desktop notification manager | Platform-native desktop notifications | `common/src/desktopMain/kotlin/chat/simplex/common/platform/Notifications.desktop.kt` |
+| Connect mobile device | Pair desktop with a mobile device for remote access | `common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt` |
+| Desktop platform abstraction | Desktop-specific PlatformInterface implementation | `common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt` |
+| Desktop app shell | Compose Desktop window, theming, lifecycle | `common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt` |
+
+---
+
+## Navigation Map
+
+### Android Navigation (2-column slide)
+
+```
+Onboarding
+ views/onboarding/SimpleXInfo.kt
+ -> SimpleXInfo -> CreateFirstProfile -> SetupDatabasePassphrase
+ -> ChooseServerOperators -> SetNotificationsMode
+ -> ChatListView (home)
+
+ChatListView (home)
+ views/chatlist/ChatListView.kt
+ -> ChatView .................. (tap conversation row, slides in)
+ -> NewChatSheet .............. (+ FAB button)
+ -> SettingsView .............. (gear icon)
+ -> UserPicker ................ (avatar tap)
+ -> TagListView ............... (tag filter bar)
+ -> ServersSummaryView ........ (server status indicator)
+ -> ShareListView ............. (share intent from external apps)
+ -> ChatHelpView .............. (empty state help)
+
+ChatView
+ views/chat/ChatView.kt
+ -> ChatInfoView .............. (contact name tap, direct chat)
+ -> GroupChatInfoView ......... (group name tap, group chat)
+ -> ActiveCallView ............ (call button, launches CallActivity)
+ -> ComposeView ............... (message input area)
+ -> ChatItemInfoView .......... (long press -> info)
+ -> MemberSupportChatView ..... (member support thread)
+ -> ScanCodeView .............. (scan QR)
+ -> CommandsMenuView .......... (/ commands)
+
+ChatInfoView
+ views/chat/ChatInfoView.kt
+ -> ContactPreferences ........ (preferences)
+ -> VerifyCodeView ............ (verify security code)
+
+GroupChatInfoView
+ views/chat/group/GroupChatInfoView.kt
+ -> GroupProfileView .......... (edit profile)
+ -> AddGroupMembersView ....... (invite members)
+ -> GroupLinkView ............. (manage group link)
+ -> MemberAdmission ........... (admission settings)
+ -> GroupPreferences .......... (group feature settings)
+ -> GroupMemberInfoView ....... (tap member)
+ -> WelcomeMessageView ........ (welcome message)
+ -> GroupReportsView .......... (view reports)
+
+NewChatSheet
+ views/newchat/NewChatSheet.kt
+ -> NewChatView ............... (QR scanner / paste link)
+ -> AddGroupView .............. (create group)
+ -> UserAddressView ........... (create SimpleX address)
+
+SettingsView
+ views/usersettings/SettingsView.kt
+ -> AppearanceView ............ (themes, wallpapers, UI)
+ -> NetworkAndServers ......... (SMP/XFTP/proxy config)
+ -> PrivacySettings ........... (privacy toggles)
+ -> NotificationsSettingsView . (notification mode)
+ -> DatabaseView .............. (export/import/encrypt)
+ -> CallSettings .............. (call preferences)
+ -> VersionInfoView ........... (about/version)
+ -> DeveloperView ............. (developer options)
+ -> HelpView .................. (help & support)
+
+UserPicker
+ views/chatlist/UserPicker.kt
+ -> UserProfilesView .......... (manage all profiles)
+ -> UserAddressView ........... (SimpleX address)
+ -> Preferences ............... (user preferences)
+ -> SettingsView .............. (app settings)
+ -> ConnectDesktopView ........ (pair with desktop)
+```
+
+### Desktop Navigation (3-column panels)
+
+```
++---------------------------+----------------------------------+----------------------------+
+| START PANEL | CENTER PANEL | END PANEL |
+| (DEFAULT_START_MODAL_ | (flexible width, min | (DEFAULT_END_MODAL_ |
+| WIDTH) | DEFAULT_MIN_CENTER_MODAL_ | WIDTH) |
+| | WIDTH) | |
++---------------------------+----------------------------------+----------------------------+
+| | | |
+| ChatListView | ChatView | ChatInfoView |
+| - chat rows | - message list | GroupChatInfoView |
+| - search | - ComposeView | GroupMemberInfoView |
+| - tag filters | - media viewer | ContactPreferences |
+| - server status | | GroupPreferences |
+| | OR (when no chat selected): | GroupProfileView |
+| UserPicker (overlay) | "No selected chat" | AddGroupMembersView |
+| - profile switcher | | MemberAdmission |
+| - quick settings | OR (when modal open): | VerifyCodeView |
+| | ModalManager.center content | SettingsView subtabs |
+| ModalManager.start | (settings, new chat, etc.) | |
+| - secondary modals | | ModalManager.end |
+| | | - detail modals |
++---------------------------+----------------------------------+----------------------------+
+
+ModalManager Placement (Desktop):
+ - ModalManager.start -> left panel overlay (settings subviews)
+ - ModalManager.center -> center panel (replaces chat, used when chatId is null)
+ - ModalManager.end -> right panel (detail/info views)
+ - ModalManager.fullscreen -> full window overlay (onboarding, auth, call)
+
+On Android, all ModalManager instances (start/center/end/fullscreen) collapse to a
+single shared ModalManager that presents modals as full-screen overlays.
+
+Desktop-only navigation targets:
+ ConnectMobileView ......... (pair with mobile device)
+ AppUpdater notice ......... (update available notification)
+ Floating terminal ......... (developer console)
+ ActiveCallView ............ (inline WebRTC call, not separate Activity)
+```
+
+---
+
+## Platform Abstraction
+
+The codebase uses two mechanisms for platform-specific behavior:
+
+### 1. `expect`/`actual` Declarations
+
+Kotlin Multiplatform `expect` declarations in `common/src/commonMain/kotlin/chat/simplex/common/platform/` with corresponding `actual` implementations in:
+- `common/src/androidMain/kotlin/chat/simplex/common/platform/*.android.kt`
+- `common/src/desktopMain/kotlin/chat/simplex/common/platform/*.desktop.kt`
+
+Key `expect`/`actual` abstractions: `appPlatform`, `BackHandler`, `VideoPlayer`, `AudioPlayer`, `RecorderNative`, `NtfManager`, `showToast`, `getKeyboardState`, `PlatformTextField`, image processing, file sharing, and more.
+
+### 2. Runtime `PlatformInterface`
+
+Defined in `common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt`, this interface provides platform-specific callbacks that cannot use `expect`/`actual` (because `android/` module code cannot be called from `common/androidMain/`). The `platform` variable is reassigned at app startup:
+- **Android:** `SimplexApp` sets `platform` to an implementation with `CallService`, notification channels, orientation locking, status bar theming, and PiP support.
+- **Desktop:** `Main.kt` sets `platform` to an implementation with `desktopShowAppUpdateNotice()`.
+
+### 3. Haskell Core (JNI/FFI)
+
+Native FFI bindings are declared in `common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt` as `external fun` declarations. These include: `chatMigrateInit`, `chatSendCmdRetry`, `chatRecvMsg`, `chatParseMarkdown`, `chatPasswordHash`, `chatWriteFile`, `chatReadFile`, `chatEncryptFile`, `chatDecryptFile`, and more. The native library (`libapp-lib`) is loaded at startup from platform-specific resource directories.
+
+---
+
+## Background Messaging (Android)
+
+Android has no equivalent to iOS NSE (Notification Service Extension). Instead, it uses:
+
+- **`SimplexService`** (`android/src/main/java/chat/simplex/app/SimplexService.kt`) -- A foreground service that keeps the Haskell core running to receive messages in real-time. Displays a persistent notification while active.
+- **`MessagesFetcherWorker`** (`android/src/main/java/chat/simplex/app/MessagesFetcherWorker.kt`) -- A WorkManager-based periodic task that wakes the app at configurable intervals to fetch messages when the foreground service is not running (battery-optimized mode).
+- **Notification modes:** Instant (foreground service always running), Periodic (WorkManager fetch every N minutes), Off.
+
+---
+
+## Related Specifications
+
+### Product Layer (this directory)
+
+- [concepts.md](concepts.md) -- Feature concept index with bidirectional code links
+- [glossary.md](glossary.md) -- Terminology definitions
+- [rules.md](rules.md) -- Business rules and constraints
+- [gaps.md](gaps.md) -- Known documentation gaps
+- Views: [chat-list](views/chat-list.md), [chat](views/chat.md), [new-chat](views/new-chat.md), [settings](views/settings.md), [call](views/call.md), [contact-info](views/contact-info.md), [group-info](views/group-info.md), [onboarding](views/onboarding.md), [user-profiles](views/user-profiles.md)
+- Flows: [messaging](flows/messaging.md), [calling](flows/calling.md), [onboarding](flows/onboarding.md), [group-lifecycle](flows/group-lifecycle.md), [connection](flows/connection.md), [file-transfer](flows/file-transfer.md)
+
+### Spec Layer
+
+- [spec/README.md](../spec/README.md) -- Technical specification overview
+- [spec/architecture.md](../spec/architecture.md) -- JNI bridge, startup, lifecycle
+- [spec/state.md](../spec/state.md) -- ChatModel, ChatsContext, Chat, AppPreferences
+- [spec/api.md](../spec/api.md) -- Command/response protocol (CC, CR, ChatError)
+- [spec/database.md](../spec/database.md) -- Migration, encryption, export/import
+- Client: [navigation](../spec/client/navigation.md), [chat-list](../spec/client/chat-list.md), [chat-view](../spec/client/chat-view.md), [compose](../spec/client/compose.md)
+- Services: [calls](../spec/services/calls.md), [theme](../spec/services/theme.md), [files](../spec/services/files.md), [notifications](../spec/services/notifications.md)
+
+### Source Entry Points
+
+- Haskell core: `../../src/Simplex/Chat/Controller.hs`, `../../src/Simplex/Chat/Types.hs`
+- Kotlin model: `common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt`
+- Kotlin API bridge: `common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt`
+- Kotlin FFI: `common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt`
+- Android entry: `android/src/main/java/chat/simplex/app/SimplexApp.kt`, `MainActivity.kt`
+- Desktop entry: `desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt`
diff --git a/apps/multiplatform/product/concepts.md b/apps/multiplatform/product/concepts.md
new file mode 100644
index 0000000000..da33bf11d7
--- /dev/null
+++ b/apps/multiplatform/product/concepts.md
@@ -0,0 +1,120 @@
+# SimpleX Chat Android & Desktop -- Concept Index
+
+> SimpleX Chat multiplatform concept index. Maps every product concept to its documentation and source code with bidirectional links.
+>
+> **Related spec:** [spec/README.md](../spec/README.md) | [spec/architecture.md](../spec/architecture.md)
+
+## Table of Contents
+
+1. [Feature Concepts](#section-1-feature-concepts)
+2. [Entity Index](#section-2-entity-index)
+
+## Executive Summary
+
+This document provides a structured mapping between product-level concepts, their documentation, and their implementation in both the Kotlin multiplatform layer and the Haskell core library. All Kotlin source paths are relative to `apps/multiplatform/`. Haskell paths use `../../src/` prefix (relative to `apps/multiplatform/`). The common source root abbreviation used below is `common/src/commonMain/kotlin/chat/simplex/common/`.
+
+---
+
+## Section 1: Feature Concepts
+
+| # | Concept | Product Docs | Spec Docs | Source Files (Kotlin) | Source Files (Haskell) |
+|---|---------|-------------|-----------|----------------------|----------------------|
+| PC1 | Chat List | [README.md](README.md) (Navigation Map) | [spec/client/chat-list.md](../spec/client/chat-list.md) | `common/.../views/chatlist/ChatListView.kt`, `ChatListNavLinkView.kt`, `ChatPreviewView.kt` | `Controller.hs` (`APIGetChats`) |
+| PC2 | Direct Chat | [README.md](README.md) (Messaging) | [spec/client/chat-view.md](../spec/client/chat-view.md) | `common/.../views/chat/ChatView.kt`, `ChatInfoView.kt` | `Types.hs` (`Contact`), `Messages.hs` |
+| PC3 | Group Chat | [README.md](README.md) (Groups) | [spec/client/chat-view.md](../spec/client/chat-view.md) | `common/.../views/chat/ChatView.kt`, `group/GroupChatInfoView.kt` | `Types.hs` (`GroupInfo`, `GroupMember`) |
+| PC4 | Message Composition | [README.md](README.md) (Messaging) | [spec/client/compose.md](../spec/client/compose.md) | `common/.../views/chat/ComposeView.kt`, `SendMsgView.kt`, `ComposeVoiceView.kt`, `ComposeImageView.kt`, `ComposeFileView.kt` | `Controller.hs` (`APISendMessages`) |
+| PC5 | Message Reactions | [README.md](README.md) (Messaging) | [spec/api.md](../spec/api.md) | `common/.../views/chat/ChatItemView.kt` (ChatItemReactions composable) | `Controller.hs` (`APIChatItemReaction`) |
+| PC6 | Message Editing | [README.md](README.md) (Messaging) | [spec/client/compose.md](../spec/client/compose.md) | `common/.../views/chat/ComposeView.kt`, `ChatItemInfoView.kt` | `Controller.hs` (`APIUpdateChatItem`) |
+| PC7 | Message Deletion | [README.md](README.md) (Messaging) | [spec/api.md](../spec/api.md) | `common/.../views/chat/item/MarkedDeletedItemView.kt`, `DeletedItemView.kt` | `Controller.hs` (`APIDeleteChatItem`) |
+| PC8 | Timed Messages | [README.md](README.md) (Messaging) | [spec/api.md](../spec/api.md) | `common/.../views/chat/item/CIChatFeatureView.kt` | `Types/Preferences.hs` (`TimedMessagesPreference`) |
+| PC9 | Voice Messages | [README.md](README.md) (Messaging) | [spec/client/compose.md](../spec/client/compose.md) | `common/.../views/chat/item/CIVoiceView.kt`, `ComposeVoiceView.kt`, `platform/RecAndPlay.kt` | `Protocol.hs` (`MCVoice`) |
+| PC10 | File Transfer | [README.md](README.md) (Messaging, Data Management) | [spec/services/files.md](../spec/services/files.md) | `common/.../views/chat/item/CIFileView.kt`, `platform/Files.kt` | `Files.hs`, `Store/Files.hs` |
+| PC11 | Link Previews | [README.md](README.md) (Messaging) | [spec/client/chat-view.md](../spec/client/chat-view.md) | `common/.../views/helpers/LinkPreviews.kt` | `Protocol.hs` (`MCLink`) |
+| PC12 | Contact Connection | [README.md](README.md) (Contacts) | [spec/api.md](../spec/api.md) | `common/.../views/newchat/NewChatView.kt`, `QRCode.kt`, `QRCodeScanner.kt`, `ConnectPlan.kt` | `Controller.hs` (`APIConnect`, `APIAddContact`) |
+| PC13 | Contact Verification | [README.md](README.md) (Contacts) | [spec/api.md](../spec/api.md) | `common/.../views/chat/VerifyCodeView.kt` | `Controller.hs` (`APIVerifyContact`) |
+| PC14 | Group Management | [README.md](README.md) (Groups) | [spec/api.md](../spec/api.md) | `common/.../views/newchat/AddGroupView.kt`, `group/GroupChatInfoView.kt`, `group/GroupProfileView.kt` | `Controller.hs` (`APINewGroup`), `Store/Groups.hs` |
+| PC15 | Group Links | [README.md](README.md) (Groups) | [spec/api.md](../spec/api.md) | `common/.../views/chat/group/GroupLinkView.kt` | `Controller.hs` (`APICreateGroupLink`) |
+| PC16 | Member Roles | [README.md](README.md) (Groups) | [spec/api.md](../spec/api.md) | `common/.../model/ChatModel.kt`, `group/GroupMemberInfoView.kt` | `Types/Shared.hs` (`GroupMemberRole`) |
+| PC17 | Audio/Video Calls | [README.md](README.md) (Calling) | [spec/services/calls.md](../spec/services/calls.md) | `common/.../views/call/CallView.kt`, `CallManager.kt`, `WebRTC.kt`, `android/.../CallService.kt`, `android/.../views/call/CallActivity.kt` | `Call.hs` (`RcvCallInvitation`, `CallType`) |
+| PC18 | Notifications | [README.md](README.md) (Background Messaging) | [spec/services/notifications.md](../spec/services/notifications.md) | `common/.../platform/NtfManager.kt`, `Notifications.kt`, `android/.../SimplexService.kt`, `android/.../MessagesFetcherWorker.kt`, `common/.../views/usersettings/NotificationsSettingsView.kt` | `Controller.hs` |
+| PC19 | User Profiles | [README.md](README.md) (User Management) | [spec/state.md](../spec/state.md) | `common/.../views/usersettings/UserProfilesView.kt`, `UserProfileView.kt`, `views/chatlist/UserPicker.kt` | `Types.hs` (`User`), `Store/Profiles.hs` |
+| PC20 | Incognito Mode | [README.md](README.md) (Contacts) | [spec/api.md](../spec/api.md) | `common/.../views/usersettings/IncognitoView.kt` | `ProfileGenerator.hs`, `Types.hs` |
+| PC21 | Hidden Profiles | [README.md](README.md) (Privacy & Security) | [spec/api.md](../spec/api.md) | `common/.../views/usersettings/HiddenProfileView.kt` | `Controller.hs` (`APIHideUser`, `APIUnhideUser`) |
+| PC22 | Local Authentication | [README.md](README.md) (Privacy & Security) | [spec/architecture.md](../spec/architecture.md) | `common/.../views/localauth/LocalAuthView.kt`, `PasscodeView.kt`, `SetAppPasscodeView.kt`, `PasswordEntry.kt`, `AppLock.kt` | N/A (client-only) |
+| PC23 | Database Encryption | [README.md](README.md) (Data Management) | [spec/database.md](../spec/database.md) | `common/.../views/database/DatabaseEncryptionView.kt`, `DatabaseView.kt`, `views/helpers/DatabaseUtils.kt` | `Controller.hs` (`APIExportArchive`) |
+| PC24 | Theme System | [README.md](README.md) (Customization) | [spec/services/theme.md](../spec/services/theme.md) | `common/.../ui/theme/ThemeManager.kt`, `Theme.kt`, `Color.kt`, `Type.kt`, `Shape.kt` | `Types/UITheme.hs` |
+| PC25 | Network Configuration | [README.md](README.md) (Network) | [spec/architecture.md](../spec/architecture.md) | `common/.../views/usersettings/networkAndServers/NetworkAndServers.kt`, `ProtocolServersView.kt`, `AdvancedNetworkSettings.kt`, `OperatorView.kt` | `Controller.hs` (`APISetNetworkConfig`) |
+| PC26 | Device Migration | [README.md](README.md) (Data Management) | [spec/database.md](../spec/database.md) | `common/.../views/migration/MigrateFromDevice.kt`, `MigrateToDevice.kt` | `Archive.hs` |
+| PC27 | Remote Desktop | [README.md](README.md) (Desktop Features) | [spec/architecture.md](../spec/architecture.md) | `common/.../views/remote/ConnectDesktopView.kt`, `ConnectMobileView.kt` | `Remote.hs`, `Remote/Types.hs` |
+| 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` |
+
+**Legend for abbreviated paths:**
+- `common/.../` expands to `common/src/commonMain/kotlin/chat/simplex/common/`
+- `android/.../` expands to `android/src/main/java/chat/simplex/app/`
+- Haskell files are in `../../src/Simplex/Chat/` (relative to `apps/multiplatform/`)
+
+---
+
+## Section 2: Entity Index
+
+Core data entities, their storage, and the operations that manage their lifecycle.
+
+| Entity | DB Table (Haskell) | Created By | Read By | Mutated By | Deleted By |
+|--------|-------------------|------------|---------|------------|------------|
+| **User** | `users` | `CreateActiveUser` in `Controller.hs` | `ListUsers`, `APISetActiveUser` in `Controller.hs` | `APISetActiveUser`, `APIHideUser`, `APIUnhideUser`, `APIMuteUser`, `APIUpdateProfile` in `Controller.hs` | `APIDeleteUser` in `Controller.hs`; `Store/Profiles.hs` |
+| **Contact** | `contacts`, `contact_profiles` | `APIAddContact`, `APIConnect` in `Controller.hs` | `APIGetChat` in `Controller.hs`; `Store/Direct.hs` (`getContact`) | `APISetContactAlias`, `APISetConnectionAlias` in `Controller.hs`; `Store/Direct.hs` | `APIDeleteChat` in `Controller.hs`; `Store/Direct.hs` (`deleteContact`) |
+| **GroupInfo** | `groups`, `group_profiles` | `APINewGroup` in `Controller.hs`; `Store/Groups.hs` (`createNewGroup`) | `APIGetChat`, `APIGroupInfo` in `Controller.hs`; `Store/Groups.hs` | `APIUpdateGroupProfile` in `Controller.hs`; `Store/Groups.hs` (`updateGroupProfile`) | `APIDeleteChat` in `Controller.hs`; `Store/Groups.hs` (`deleteGroup`) |
+| **GroupMember** | `group_members`, `contact_profiles` | `APIAddMember`, `APIJoinGroup` in `Controller.hs`; `Store/Groups.hs` (`createNewGroupMember`) | `APIListMembers` in `Controller.hs`; `Store/Groups.hs` (`getGroupMembers`) | `APIMembersRole` in `Controller.hs`; `Store/Groups.hs` (`updateGroupMemberRole`) | `APIRemoveMembers` in `Controller.hs`; `Store/Groups.hs` (`deleteGroupMember`) |
+| **ChatItem** | `chat_items`, `chat_item_versions` | `APISendMessages` in `Controller.hs`; `Store/Messages.hs` (`createNewChatItem`) | `APIGetChat`, `APIGetChatItems` in `Controller.hs`; `Store/Messages.hs` (`getChatItems`) | `APIUpdateChatItem`, `APIChatItemReaction` in `Controller.hs`; `Store/Messages.hs` (`updateChatItem`) | `APIDeleteChatItem` in `Controller.hs`; `Store/Messages.hs` (`deleteChatItem`) |
+| **Connection** | `connections` | `createConnection` via SMP agent; `Store/Connections.hs` | `Store/Connections.hs` (`getConnectionEntity`) | `Store/Connections.hs` (`updateConnectionStatus`) | `Store/Connections.hs` (`deleteConnection`) |
+| **FileTransfer** | `files`, `snd_files`, `rcv_files`, `xftp_file_descriptions` | `APISendMessages` (with file), `ReceiveFile` in `Controller.hs`; `Store/Files.hs` | `Store/Files.hs` (`getFileTransfer`) | `Store/Files.hs` (`updateFileStatus`, `updateFileProgress`) | `Store/Files.hs` (`deleteFileTransfer`) |
+| **GroupLink** | `user_contact_links` | `APICreateGroupLink` in `Controller.hs`; `Store/Groups.hs` | `APIGetGroupLink` in `Controller.hs`; `Store/Groups.hs` | N/A (recreated on change) | `APIDeleteGroupLink` in `Controller.hs`; `Store/Groups.hs` |
+| **ChatTag** | `chat_tags`, `chat_tags_chats` | `APICreateChatTag` in `Controller.hs` | `APIGetChats` in `Controller.hs` | `APIUpdateChatTag`, `APISetChatTags` in `Controller.hs` | `APIDeleteChatTag` in `Controller.hs` |
+| **RcvCallInvitation** | In-memory (not persisted) | Received via `XCallInv` message in `Library/Subscriber.hs`; stored in `ChatModel.activeCallInvitation` | `CallManager.kt`, `IncomingCallAlertView.kt` | Updated on call accept/reject in `CallManager.kt` | Removed on call end/reject; `Controller.hs` |
+
+---
+
+## Platform-Specific Source Index
+
+Key files that exist only on one platform, grouped by concern.
+
+### Android-Only
+
+| File | Purpose |
+|------|---------|
+| `android/src/main/java/chat/simplex/app/SimplexApp.kt` | Application subclass, PlatformInterface setup, Haskell init |
+| `android/src/main/java/chat/simplex/app/MainActivity.kt` | Main Activity, deep link handling, lifecycle |
+| `android/src/main/java/chat/simplex/app/SimplexService.kt` | Foreground service for persistent messaging |
+| `android/src/main/java/chat/simplex/app/MessagesFetcherWorker.kt` | WorkManager periodic message fetch |
+| `android/src/main/java/chat/simplex/app/CallService.kt` | Foreground service for active calls |
+| `android/src/main/java/chat/simplex/app/views/call/CallActivity.kt` | Dedicated Activity for call UI |
+| `android/src/main/java/chat/simplex/app/model/NtfManager.android.kt` | Android notification channels and manager |
+| `common/src/androidMain/kotlin/chat/simplex/common/platform/*.android.kt` | All `actual` implementations for Android |
+
+### Desktop-Only
+
+| File | Purpose |
+|------|---------|
+| `desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt` | JVM entry point, Haskell/VLC init, PlatformInterface setup |
+| `common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt` | Compose Desktop window creation and lifecycle |
+| `common/src/desktopMain/kotlin/chat/simplex/common/StoreWindowState.kt` | Window position/size persistence |
+| `common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/AppUpdater.kt` | In-app update checker |
+| `common/src/desktopMain/kotlin/chat/simplex/common/platform/Videos.desktop.kt` | VLC-based video detection |
+| `common/src/desktopMain/kotlin/chat/simplex/common/platform/VideoPlayer.desktop.kt` | VLC video player implementation |
+| `common/src/desktopMain/kotlin/chat/simplex/common/platform/Platform.desktop.kt` | Desktop platform detection (Linux/macOS/Windows) |
+| `common/src/desktopMain/kotlin/chat/simplex/common/platform/*.desktop.kt` | All `actual` implementations for Desktop |
+
+---
+
+## Cross-References
+
+- Product overview: [README.md](README.md)
+- Haskell core controller: `../../src/Simplex/Chat/Controller.hs`
+- Haskell core types: `../../src/Simplex/Chat/Types.hs`
+- Haskell store layer: `../../src/Simplex/Chat/Store/` (`Direct.hs`, `Groups.hs`, `Messages.hs`, `Files.hs`, `Profiles.hs`, `Connections.hs`)
+- Kotlin model: `common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt`
+- Kotlin API bridge: `common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt`
+- Kotlin FFI layer: `common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt`
+- Platform abstraction: `common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt` (`PlatformInterface`)
diff --git a/apps/multiplatform/product/flows/calling.md b/apps/multiplatform/product/flows/calling.md
new file mode 100644
index 0000000000..fae7f42031
--- /dev/null
+++ b/apps/multiplatform/product/flows/calling.md
@@ -0,0 +1,220 @@
+# Calling Flow
+
+> **Related spec:** [spec/services/calls.md](../../spec/services/calls.md)
+
+## Overview
+
+SimpleX Chat supports audio and video calls using WebRTC, with signaling delivered over the existing SMP messaging channels. Calls are end-to-end encrypted with an additional shared key layer on top of WebRTC's SRTP encryption.
+
+The architecture differs by platform:
+- **Android**: Calls run in a dedicated `CallActivity` (separate from `MainActivity`) with a `WebView` hosting the WebRTC JavaScript. A foreground `CallService` keeps the process alive and shows a persistent notification.
+- **Desktop**: Calls open the system browser pointed at a local NanoHTTPD/NanoWSD embedded server on `localhost:50395`, which serves the WebRTC HTML/JS and communicates with the app via WebSocket.
+
+Both platforms share a common signaling flow through the Haskell core API.
+
+## Prerequisites
+
+- Both parties must have an established direct contact connection.
+- Microphone permission is required; camera permission is required for video calls.
+- On Android, the `CallOnLockScreen` preference controls lock-screen call behavior: `DISABLE`, `SHOW`, or `ACCEPT`.
+
+---
+
+## 1. Outgoing Call (Caller Side)
+
+### 1.1 Initiate Call
+
+1. User taps the audio or video call button in `ChatView`.
+2. `startChatCall(remoteHostId, chatInfo, media)` is called (in `ChatView.kt`).
+3. A `Call` object is created with `callState = CallState.WaitCapabilities`:
+ ```kotlin
+ Call(
+ remoteHostId = remoteHostId,
+ contact = contact,
+ callUUID = null,
+ callState = CallState.WaitCapabilities,
+ initialCallType = media, // Audio or Video
+ userProfile = profile,
+ androidCallState = platform.androidCreateActiveCallState()
+ )
+ ```
+4. `ChatModel.activeCall` is set and `ChatModel.showCallView` is set to `true`.
+5. A `WCallCommand.Capabilities(media)` command is added to `ChatModel.callCommand`.
+
+### 1.2 WebRTC Capabilities Response
+
+1. The WebRTC engine (WebView on Android, browser on Desktop) receives the `Capabilities` command.
+2. It responds with `WCallResponse.Capabilities(capabilities)` containing encryption support info.
+3. The app calls `ChatController.apiSendCallInvitation(rh, contact, callType)` to send the invitation via SMP.
+4. Call state transitions to `CallState.InvitationSent`.
+5. A connecting sound starts playing via `CallSoundsPlayer.startConnectingCallSound`.
+
+### 1.3 Offer Exchange
+
+1. When the callee accepts, the WebRTC engine generates an offer.
+2. `WCallResponse.Offer(offer, iceCandidates, capabilities)` is received.
+3. `ChatController.apiSendCallOffer(rh, contact, rtcSession, rtcIceCandidates, media, capabilities)` sends it.
+4. Call state transitions to `CallState.OfferSent`.
+
+### 1.4 Answer and Connection
+
+1. The callee's answer arrives via SMP as a chat event.
+2. The app dispatches `WCallCommand.Answer(answer, iceCandidates)` to the WebRTC engine.
+3. Call state transitions to `CallState.Negotiated`, then to `CallState.Connected` once the ICE connection succeeds.
+4. `Call.connectedAt` is set to the current timestamp.
+
+---
+
+## 2. Incoming Call (Callee Side)
+
+### 2.1 Receive Invitation
+
+1. An incoming call event arrives from the core as `CR.CallInvitation`.
+2. `CallManager.reportNewIncomingCall(invitation)` is called.
+3. A `RcvCallInvitation` is stored in `ChatModel.callInvitations` keyed by contact ID.
+4. If the invitation is recent (within 3 minutes), a system notification is shown and `ChatModel.activeCallInvitation` is set.
+5. On Android, `CallActivity` may be launched on the lock screen if `callOnLockScreen` is `SHOW` or `ACCEPT`.
+
+### 2.2 Accept Call
+
+1. User taps "Accept" on the `IncomingCallAlertView` or lock-screen alert.
+2. `CallManager.acceptIncomingCall(invitation)` is called.
+3. If another call is active, it is ended first (with `switchingCall` flag set).
+4. A new `Call` is created with `callState = CallState.InvitationAccepted`.
+5. ICE servers are loaded from preferences (`getIceServers()`).
+6. `WCallCommand.Start(media, aesKey, iceServers, relay)` is dispatched to the WebRTC engine.
+7. The call invitation is removed from `callInvitations` and the notification is cancelled.
+
+### 2.3 Reject Call
+
+1. User taps "Reject" or the invitation times out.
+2. `CallManager.endCall(invitation)` is called.
+3. `ChatController.apiRejectCall(rh, contact)` notifies the caller.
+4. The invitation is removed from `callInvitations`.
+
+---
+
+## 3. Call State Machine
+
+```
+Outgoing: WaitCapabilities -> InvitationSent -> OfferSent -> AnswerReceived -> Negotiated -> Connected -> Ended
+Incoming: InvitationAccepted -> OfferReceived -> Negotiated -> Connected -> Ended
+```
+
+| State | Description |
+|-------|-------------|
+| `WaitCapabilities` | Querying local WebRTC capabilities |
+| `InvitationSent` | Caller sent invitation via SMP |
+| `InvitationAccepted` | Callee accepted, starting WebRTC |
+| `OfferSent` | Caller sent SDP offer |
+| `OfferReceived` | Callee received SDP offer |
+| `AnswerReceived` | Caller received SDP answer |
+| `Negotiated` | ICE negotiation complete |
+| `Connected` | Media flowing |
+| `Ended` | Call terminated |
+
+---
+
+## 4. Ending a Call
+
+1. User taps the end-call button, or the remote side ends the call.
+2. `CallManager.endCall(call)` is called.
+3. `ChatController.apiEndCall(rh, contact)` notifies the remote side via SMP.
+4. `ChatModel.showCallView` is set to `false`.
+5. `ChatModel.activeCall` is set to `null`.
+6. On Android, `CallService` is stopped and the `WebView` is destroyed.
+7. On Desktop, `WCallCommand.End` is sent to the browser via WebSocket, and the NanoWSD server is stopped.
+
+---
+
+## 5. Android-Specific: CallActivity and CallService
+
+### 5.1 CallActivity
+
+- `CallActivity` is a separate `ComponentActivity` (not `MainActivity`).
+- It is launched via `platform.androidStartCallActivity(acceptCall, remoteHostId, chatId)`.
+- It hosts `ActiveCallView` with a `WebView` for WebRTC.
+- Supports lock-screen display: `setShowWhenLocked(true)` and `setTurnScreenOn(true)`.
+- Supports Picture-in-Picture (PiP) mode for video calls.
+ - On Android 12+, PiP auto-enters when the user navigates away.
+ - On older versions, PiP is entered via `enterPictureInPictureMode()` on `onUserLeaveHint`.
+ - PiP layout switches to `LayoutType.RemoteVideo` to show only the remote video feed.
+- The activity finishes itself when both `invitation == null` and (`!showCallView || call == null`) and `!switchingCall`.
+
+### 5.2 CallService
+
+- `CallService` is a foreground `Service` that keeps the process alive during calls.
+- Started via `CallService.startService()` which calls `ContextCompat.startForegroundService`.
+- Acquires a partial `WakeLock` to prevent CPU sleep.
+- Shows a persistent notification with:
+ - Contact name and call type (audio/video).
+ - An "End Call" action button.
+ - A chronometer showing call duration (from `connectedAt`).
+- The notification taps open `CallActivity`.
+- Foreground service type includes `MICROPHONE`, `CAMERA` (if video), and `MEDIA_PLAYBACK`.
+
+---
+
+## 6. Desktop-Specific: Browser-Based WebRTC
+
+### 6.1 NanoWSD Embedded Server
+
+1. When a call starts, `startServer(onResponse)` creates a `NanoWSD` server on `localhost:50395`.
+2. The server serves static WebRTC HTML/JS from bundled resources at `/assets/www/desktop/call.html`.
+3. The system browser is opened to `http://localhost:50395/simplex/call/`.
+
+### 6.2 WebSocket Communication
+
+1. The browser page connects back via WebSocket to the same `localhost:50395` server.
+2. Commands from the app to the browser are serialized as `WVAPICall(corrId, command)` JSON.
+3. Responses from the browser arrive as `WVAPIMessage(corrId, resp, command)` JSON.
+4. The `WebRTCController` composable manages the command queue:
+ - Collects commands from `ChatModel.callCommand` (a `SnapshotStateList`).
+ - Sends them to the browser via the WebSocket connection.
+ - Processes responses through the same `WCallResponse` handling as Android.
+5. On dispose, `WCallCommand.End` is sent, the server is stopped, and connections are cleared.
+
+---
+
+## 7. Common Signaling API
+
+| API Function | Purpose |
+|-------------|---------|
+| `apiSendCallInvitation(rh, contact, callType)` | Send call invitation via SMP |
+| `apiRejectCall(rh, contact)` | Reject incoming call |
+| `apiSendCallOffer(rh, contact, rtcSession, rtcIceCandidates, media, capabilities)` | Send SDP offer |
+| `apiSendCallAnswer(rh, contact, rtcSession, rtcIceCandidates)` | Send SDP answer |
+| `apiSendCallExtraInfo(rh, contact, rtcIceCandidates)` | Send additional ICE candidates |
+| `apiEndCall(rh, contact)` | End active call |
+| `apiCallStatus(rh, contact, status)` | Report WebRTC connection status |
+
+---
+
+## 8. In-Call Media Controls
+
+During an active call, the user can toggle media sources via `WCallCommand.Media(source, enable)`:
+
+| Source | Control |
+|--------|---------|
+| `CallMediaSource.Mic` | Mute/unmute microphone |
+| `CallMediaSource.Camera` | Enable/disable camera |
+| `CallMediaSource.ScreenAudio` | Screen share audio |
+| `CallMediaSource.ScreenVideo` | Screen share video |
+
+Camera switching (front/back) is done via `WCallCommand.Camera(VideoCamera.User / VideoCamera.Environment)`.
+
+---
+
+## Key Types Reference
+
+| Type | Location | Purpose |
+|------|----------|---------|
+| `Call` | `views/call/WebRTC.kt` | Active call state: contact, callState, media sources, encryption |
+| `CallState` | `views/call/WebRTC.kt` | Enum: WaitCapabilities through Ended |
+| `RcvCallInvitation` | `views/call/WebRTC.kt` | Incoming call invitation with contact, callType, sharedKey |
+| `CallManager` | `views/call/CallManager.kt` | Manages call lifecycle: accept, end, report |
+| `WCallCommand` | `views/call/WebRTC.kt` | Commands to WebRTC engine: Capabilities, Start, Offer, Answer, Ice, Media, Camera, End |
+| `WCallResponse` | `views/call/WebRTC.kt` | Responses from WebRTC: Capabilities, Offer, Answer, Ice, Connection, Connected, End |
+| `CallActivity` | `android/.../views/call/CallActivity.kt` | Android Activity hosting the call UI and WebView |
+| `CallService` | `android/.../CallService.kt` | Android foreground Service for call persistence |
+| `NanoWSD` | `desktopMain/.../views/call/CallView.desktop.kt` | Desktop embedded HTTP+WebSocket server |
diff --git a/apps/multiplatform/product/flows/connection.md b/apps/multiplatform/product/flows/connection.md
new file mode 100644
index 0000000000..1b1123b535
--- /dev/null
+++ b/apps/multiplatform/product/flows/connection.md
@@ -0,0 +1,233 @@
+# Connection Flow
+
+> **Related spec:** [spec/client/navigation.md](../../spec/client/navigation.md) | [spec/api.md](../../spec/api.md)
+
+## Overview
+
+Establishing a contact connection in SimpleX Chat follows an invitation-link model. One party creates a connection link (one-time invitation or long-term address), shares it out-of-band, and the other party connects via that link. The process uses SMP queues for the handshake, with no central server involved in identity management.
+
+Connections support incognito mode, where a random profile is used per-connection instead of the user's real profile.
+
+## Prerequisites
+
+- Chat is initialized and running.
+- An active user profile exists.
+- For connecting: a valid SimpleX connection link (invitation or address).
+
+---
+
+## 1. Creating a Connection Link (Inviter Side)
+
+### 1.1 One-Time Invitation Link
+
+1. User navigates to "New Chat" and selects "Add Contact" (or uses the "+" action).
+2. `ChatController.apiAddContact(rh, incognito)` is called:
+
+```kotlin
+suspend fun apiAddContact(rh: Long?, incognito: Boolean): Pair?, (() -> Unit)?>
+```
+
+3. Internally, `CC.APIAddContact(userId, incognito)` is sent to the core.
+4. The core creates a new SMP queue pair and returns:
+ - `CR.Invitation` with `connLinkInvitation: CreatedConnLink` and `connection: PendingContactConnection`.
+5. The `CreatedConnLink` contains the invitation URI (long form and short link).
+6. The link is displayed as a QR code in `NewChatView` and can be copied or shared.
+7. A `PendingContactConnection` appears in the chat list while waiting.
+
+### 1.2 Long-Term Contact Address
+
+1. User goes to Settings and creates a SimpleX address.
+2. This creates a persistent address link that multiple people can use.
+3. Incoming connection requests from the address require explicit acceptance (see section 4).
+
+---
+
+## 2. Connecting via Link (Connector Side)
+
+### 2.1 Preview the Connection Plan
+
+Before connecting, the link is analyzed:
+
+```kotlin
+suspend fun apiConnectPlan(rh: Long?, connLink: String, inProgress: MutableState): Pair?
+```
+
+1. User pastes or scans a link.
+2. `apiConnectPlan` sends `CC.APIConnectPlan(userId, connLink)` to the core.
+3. The core resolves short links, validates the link, and returns a `ConnectionPlan`:
+
+```kotlin
+sealed class ConnectionPlan {
+ class InvitationLink(val invitationLinkPlan: InvitationLinkPlan): ConnectionPlan()
+ class ContactAddress(val contactAddressPlan: ContactAddressPlan): ConnectionPlan()
+ class GroupLink(val groupLinkPlan: GroupLinkPlan): ConnectionPlan()
+ class Error(val chatError: ChatError): ConnectionPlan()
+}
+```
+
+4. For `InvitationLinkPlan`:
+ - `Ok`: Fresh invitation, safe to connect.
+ - `OwnLink`: User's own link, alert shown.
+ - `Connecting(contact_)`: Already connecting to this contact.
+ - `Known(contact)`: Already connected, existing contact shown.
+
+5. For `ContactAddressPlan`:
+ - `Ok`: Fresh address, safe to connect.
+ - `OwnLink`: User's own address.
+ - `ConnectingConfirmReconnect`: Was connecting, offer to retry.
+ - `ConnectingProhibit(contact)`: Connection in progress, cannot duplicate.
+ - `Known(contact)`: Already a contact.
+ - `ContactViaAddress(contact)`: Contact already exists via this address.
+
+6. For `GroupLinkPlan`:
+ - `Ok`: Fresh group link, safe to join.
+ - `OwnLink(groupInfo)`: User's own group.
+ - `ConnectingConfirmReconnect`: Was connecting, offer to retry.
+ - `ConnectingProhibit(groupInfo_)`: Connection in progress.
+ - `Known(groupInfo)`: Already a member.
+
+### 2.2 High-Level Connect Flow: planAndConnect
+
+The `planAndConnect` function in `ConnectPlan.kt` orchestrates the full connect experience:
+
+```kotlin
+suspend fun planAndConnect(
+ rhId: Long?,
+ shortOrFullLink: String,
+ close: (() -> Unit)?,
+ cleanup: (() -> Unit)? = null,
+ filterKnownContact: ((Contact) -> Unit)? = null,
+ filterKnownGroup: ((GroupInfo) -> Unit)? = null,
+): CompletableDeferred
+```
+
+1. A progress indicator is shown.
+2. `apiConnectPlan` is called to analyze the link.
+3. Based on the plan type, the appropriate UI is shown:
+ - For `Ok` plans: proceed to `apiConnect`.
+ - For `Known`: navigate to the existing contact/group.
+ - For `OwnLink`: show alert.
+ - For `Connecting`: show reconnect confirmation or prohibit.
+4. Returns a `CompletableDeferred` indicating success.
+
+### 2.3 Execute Connection
+
+```kotlin
+suspend fun apiConnect(rh: Long?, incognito: Boolean, connLink: CreatedConnLink): PendingContactConnection?
+```
+
+1. `CC.APIConnect(userId, incognito, connLink)` is sent to the core.
+2. The core initiates the SMP handshake:
+ - For invitation links: `CR.SentConfirmation` is returned.
+ - For contact addresses: `CR.SentInvitation` is returned.
+3. A `PendingContactConnection` is returned and appears in the chat list.
+4. The connect progress indicator is shown via `ConnectProgressManager`.
+
+---
+
+## 3. Connection Handshake Completion
+
+### 3.1 For Invitation Links
+
+1. After the connector sends confirmation, the inviter's core receives it.
+2. Both sides complete the SMP handshake automatically.
+3. A `CR.ContactConnected` event is received on both sides.
+4. The `PendingContactConnection` in the chat list is replaced by a full `Contact`.
+5. Both parties can now exchange messages.
+
+### 3.2 For Contact Addresses
+
+1. The connector's confirmation arrives as a `ContactRequest` on the address owner's side.
+2. The address owner must explicitly accept or reject (see section 4).
+3. Once accepted, the handshake completes and `CR.ContactConnected` fires.
+
+---
+
+## 4. Contact Request Acceptance
+
+### 4.1 Accept a Contact Request
+
+```kotlin
+suspend fun apiAcceptContactRequest(rh: Long?, incognito: Boolean, contactReqId: Long): Contact?
+```
+
+1. The address owner sees a contact request notification in the chat list.
+2. User taps to open and selects "Accept".
+3. `CC.ApiAcceptContact(incognito, contactReqId)` is sent to the core.
+4. The core responds with `CR.AcceptingContactRequest` and a `Contact` object.
+5. The SMP handshake continues; once complete, `CR.ContactConnected` fires.
+6. The `incognito` flag determines whether the real profile or a random profile is shared.
+
+### 4.2 Reject a Contact Request
+
+```kotlin
+suspend fun apiRejectContactRequest(rh: Long?, contactReqId: Long): Contact?
+```
+
+1. User selects "Reject" on the contact request.
+2. `CC.ApiRejectContact(contactReqId)` is sent to the core.
+3. The core responds with `CR.ContactRequestRejected`.
+4. The contact request is removed from the chat list.
+5. The connector's side eventually times out or receives an error.
+
+---
+
+## 5. Incognito Mode
+
+### 5.1 Per-Connection Incognito
+
+1. The `incognito` parameter is available on both `apiAddContact` and `apiConnect`.
+2. When `incognito = true`:
+ - A random display name is generated for this connection.
+ - The real user profile is not shared with the contact.
+ - The incognito profile is stored per-connection in the database.
+3. The global incognito toggle is in `AppPreferences.incognito`.
+4. Incognito status is visible in the chat info view.
+
+### 5.2 Accept with Incognito
+
+1. When accepting a contact request with `incognito = true`, a random profile is used.
+2. The accepted contact only sees the random profile.
+3. The user can have some contacts with real profile and others with incognito profiles.
+
+---
+
+## 6. Connection Progress and UI
+
+### 6.1 ConnectProgressManager
+
+```kotlin
+object ConnectProgressManager {
+ fun startConnectProgress(text: String, onCancel: (() -> Unit)? = null)
+ fun stopConnectProgress()
+ fun cancelConnectProgress()
+}
+```
+
+1. When a connection is initiated, `startConnectProgress` is called.
+2. After a 1-second delay, a progress indicator appears if the operation is still in progress.
+3. On completion (success or failure), `stopConnectProgress` is called.
+4. The user can cancel via `cancelConnectProgress`.
+
+### 6.2 Pending Connection States
+
+While connecting, the chat list shows a `PendingContactConnection` with status:
+- Waiting for the other party to scan/use the link.
+- Connecting (handshake in progress).
+- Connected (transitions to a full Contact chat).
+
+---
+
+## Key Types Reference
+
+| Type | Location | Purpose |
+|------|----------|---------|
+| `CreatedConnLink` | `model/SimpleXAPI.kt` | Connection link with full URI and short link |
+| `PendingContactConnection` | `model/ChatModel.kt` | In-progress connection shown in chat list |
+| `ConnectionPlan` | `model/SimpleXAPI.kt` | Sealed class: InvitationLink, ContactAddress, GroupLink, Error |
+| `InvitationLinkPlan` | `model/SimpleXAPI.kt` | Ok, OwnLink, Connecting, Known |
+| `ContactAddressPlan` | `model/SimpleXAPI.kt` | Ok, OwnLink, ConnectingConfirmReconnect, ConnectingProhibit, Known |
+| `GroupLinkPlan` | `model/SimpleXAPI.kt` | Ok, OwnLink, ConnectingConfirmReconnect, ConnectingProhibit, Known |
+| `ConnectProgressManager` | `model/ChatModel.kt` | Manages connect progress indicator with timeout |
+| `Contact` | `model/ChatModel.kt` | Established contact with profile, connection status |
+| `ContactRequest` | `model/ChatModel.kt` | Pending inbound contact request |
diff --git a/apps/multiplatform/product/flows/file-transfer.md b/apps/multiplatform/product/flows/file-transfer.md
new file mode 100644
index 0000000000..edbb565c07
--- /dev/null
+++ b/apps/multiplatform/product/flows/file-transfer.md
@@ -0,0 +1,252 @@
+# File Transfer Flow
+
+> **Related spec:** [spec/services/files.md](../../spec/services/files.md)
+
+## Overview
+
+SimpleX Chat transfers files using two protocols based on file size: inline delivery through SMP messages for small files, and XFTP (SimpleX File Transfer Protocol) for larger files. All locally stored files can be AES-encrypted via CryptoFile. The system supports automatic receiving of small media, manual download for larger files, and cancellation at any stage.
+
+## Prerequisites
+
+- An active chat connection (direct contact or group).
+- Sufficient storage space on the device.
+- For XFTP: network connectivity to XFTP relay servers.
+
+---
+
+## 1. File Size Thresholds and Constants
+
+| Constant | Value | Purpose |
+|----------|-------|---------|
+| `MAX_IMAGE_SIZE` | 261,120 bytes (255 KB) | Maximum inline image thumbnail size (base64 in message body) |
+| `MAX_IMAGE_SIZE_AUTO_RCV` | 522,240 bytes (510 KB) | Auto-receive threshold for images |
+| `MAX_VOICE_SIZE_AUTO_RCV` | 522,240 bytes (510 KB) | Auto-receive threshold for voice messages |
+| `MAX_VIDEO_SIZE_AUTO_RCV` | 1,047,552 bytes (1023 KB) | Auto-receive threshold for video thumbnails |
+| `MAX_FILE_SIZE_SMP` | 8,000,000 bytes (~7.6 MB) | Maximum file size for SMP inline transfer |
+| `MAX_FILE_SIZE_XFTP` | 1,073,741,824 bytes (1 GB) | Maximum file size for XFTP transfer |
+| `MAX_FILE_SIZE_LOCAL` | `Long.MAX_VALUE` | No limit for local files |
+
+These constants are defined in `views/helpers/Utils.kt`.
+
+The core decides the transfer protocol:
+- Files within the SMP inline threshold are embedded directly in SMP messages.
+- Files exceeding the inline threshold (up to 1 GB) use XFTP with chunked, encrypted upload/download through relay servers.
+
+---
+
+## 2. CryptoFile Encryption
+
+### 2.1 Overview
+
+When `privacyEncryptLocalFiles` is enabled (default: `true`), files stored on device are AES-GCM encrypted. The encryption/decryption is performed via JNI calls to the Haskell core.
+
+### 2.2 Key Types
+
+```kotlin
+// model/ChatModel.kt
+@Serializable
+data class CryptoFileArgs(
+ val fileKey: String, // AES-256 key (base64)
+ val fileNonce: String // GCM nonce (base64)
+)
+
+@Serializable
+data class CryptoFile {
+ val filePath: String
+ val cryptoArgs: CryptoFileArgs? // null for unencrypted files
+}
+```
+
+### 2.3 Write (Encrypt)
+
+```kotlin
+fun writeCryptoFile(path: String, data: ByteArray): CryptoFileArgs
+```
+
+1. `ChatController.getChatCtrl()` obtains the active controller handle.
+2. Data is placed in a `DirectByteBuffer`.
+3. `chatWriteFile(ctrl, path, buffer)` is called via JNI.
+4. The core generates a random AES key and nonce, encrypts the data, writes to `path`.
+5. Returns `CryptoFileArgs(fileKey, fileNonce)` needed for decryption.
+6. On error, throws an exception with the error message.
+
+### 2.4 Read (Decrypt)
+
+```kotlin
+fun readCryptoFile(path: String, cryptoArgs: CryptoFileArgs): ByteArray
+```
+
+1. `chatReadFile(path, cryptoArgs.fileKey, cryptoArgs.fileNonce)` is called via JNI.
+2. Returns a two-element array: `[status: Int, data: ByteArray]`.
+3. If `status == 0`, the decrypted data is returned.
+4. Otherwise, an exception is thrown with the error message.
+
+### 2.5 File-to-File Encryption
+
+```kotlin
+fun encryptCryptoFile(fromPath: String, toPath: String): CryptoFileArgs
+```
+
+Encrypts a plaintext file at `fromPath` to an encrypted file at `toPath`. Used when saving user-selected files to the app's encrypted storage.
+
+### 2.6 File-to-File Decryption
+
+```kotlin
+fun decryptCryptoFile(fromPath: String, cryptoArgs: CryptoFileArgs, toPath: String)
+```
+
+Decrypts an encrypted file at `fromPath` to plaintext at `toPath`. Used when exporting/sharing files.
+
+---
+
+## 3. Sending Files
+
+### 3.1 Attach and Send via ComposeView
+
+1. User attaches a file via the file picker.
+2. File size is validated: `fileSize <= MAX_FILE_SIZE_XFTP` (1 GB).
+3. If valid, `ComposeState.preview` is set to `ComposePreview.FilePreview(fileName, uri)`.
+4. If too large, an alert is shown with the maximum supported size.
+5. On send, the file is copied to the app files directory.
+6. If `privacyEncryptLocalFiles` is enabled, the file is encrypted via `encryptCryptoFile`, producing a `CryptoFile` with `cryptoArgs`.
+7. A `ComposedMessage` is created with:
+ - `fileSource`: the `CryptoFile` (path + optional cryptoArgs).
+ - `msgContent`: `MsgContent.MCFile(text)` for generic files, `MsgContent.MCImage(text, thumbnail)` for images, `MsgContent.MCVideo(text, thumbnail, duration)` for videos, or `MsgContent.MCVoice(text, duration)` for voice.
+8. `ChatController.apiSendMessages(...)` dispatches the message.
+9. The core determines the transfer protocol and begins the upload.
+
+### 3.2 Standalone File Upload (XFTP)
+
+For uploading files outside of a chat message context:
+
+```kotlin
+suspend fun uploadStandaloneFile(user: UserLike, file: CryptoFile, ctrl: ChatCtrl? = null): Pair
+```
+
+1. `CC.ApiUploadStandaloneFile(userId, file)` is sent to the core.
+2. On success, `CR.SndStandaloneFileCreated` returns a `FileTransferMeta`.
+3. The meta contains a file description URI that can be shared for download.
+
+### 3.3 Upload Progress
+
+1. The core emits `SndFileProgressXFTP` events periodically during upload.
+2. `CIFileStatus` on the chat item transitions through:
+ - `SndStored` (queued)
+ - `SndTransfer(sndProgress, sndTotal)` (uploading)
+ - `SndComplete` (upload finished, link sent)
+3. The UI updates the progress indicator on the file attachment.
+
+---
+
+## 4. Receiving Files
+
+### 4.1 Auto-Receive
+
+When `privacyAcceptImages` is enabled (default: `true`), small media files are auto-received:
+
+1. On receiving a message with a file attachment, the auto-receive logic checks:
+ - `MCImage` files with `fileSize <= MAX_IMAGE_SIZE_AUTO_RCV` (510 KB)
+ - `MCVideo` files with `fileSize <= MAX_VIDEO_SIZE_AUTO_RCV` (1023 KB)
+ - `MCVoice` files with `fileSize <= MAX_VOICE_SIZE_AUTO_RCV` (510 KB) and not already accepted
+2. If criteria are met, `receiveFile` is called automatically.
+
+### 4.2 Manual Receive
+
+For files that are not auto-received:
+
+1. The chat item shows a download button with file size info.
+2. File size is validated: `fileSizeValid(file)` checks `file.fileSize <= getMaxFileSize(file.fileProtocol)`.
+3. User taps the download button.
+4. `ChatController.receiveFile(rhId, user, fileId, userApprovedRelays, auto)` is called:
+
+```kotlin
+suspend fun receiveFile(rhId: Long?, user: UserLike, fileId: Long, userApprovedRelays: Boolean = false, auto: Boolean = false)
+```
+
+5. This delegates to `receiveFiles` which handles relay approval:
+
+```kotlin
+suspend fun receiveFiles(rhId: Long?, user: UserLike, fileIds: List, userApprovedRelays: Boolean = false, auto: Boolean = false)
+```
+
+6. For each file, `CC.ReceiveFile(fileId, userApprovedRelays, encrypted, inline)` is sent to the core.
+7. If the file requires unapproved XFTP relays, the user is prompted to approve them.
+8. Relay approval errors (`FileError.Auth` with `SMP AUTH` and `PROXY BROKER`) trigger relay approval alerts.
+9. Other errors are collected and shown after all files are processed.
+
+### 4.3 Batch Receive
+
+Multiple files can be received at once:
+
+```kotlin
+suspend fun receiveFiles(rhId: Long?, user: UserLike, fileIds: List, ...)
+```
+
+1. Iterates through all `fileIds`.
+2. Files needing relay approval are batched and prompted once.
+3. After approval, those files are retried with `userApprovedRelays = true`.
+4. Errors for individual files are aggregated.
+
+### 4.4 Download Progress
+
+1. The core emits `RcvFileProgressXFTP` events during download.
+2. `CIFileStatus` transitions through:
+ - `RcvAccepted` (download initiated)
+ - `RcvTransfer(rcvProgress, rcvTotal)` (downloading)
+ - `RcvComplete` (download finished)
+3. On completion, if the file is encrypted, it remains encrypted on disk with `cryptoArgs` stored in the database.
+4. When the user opens/views the file, `readCryptoFile` or `decryptCryptoFile` is called on demand.
+
+---
+
+## 5. Cancelling a File Transfer
+
+### 5.1 Cancel via API
+
+```kotlin
+suspend fun cancelFile(rh: Long?, user: User, fileId: Long)
+```
+
+1. `apiCancelFile(rh, fileId)` sends `CC.CancelFile(fileId)` to the core.
+2. The core cancels any in-progress upload or download.
+3. On success, the chat item is updated via `chatItemSimpleUpdate`.
+4. `cleanupFile(chatItem)` removes any partial local files.
+
+### 5.2 Cancel via UI
+
+1. User long-presses a file message and selects "Cancel".
+2. `cancelFileAlertDialog(fileId, cancelFile, cancelAction)` shows a confirmation dialog.
+3. `CancelAction` provides the appropriate alert text based on direction (sending/receiving).
+4. On confirmation, `cancelFile` is called.
+
+### 5.3 Compose Cancel
+
+Before sending, user can cancel the file attachment:
+
+1. User taps the "X" on the file preview in the compose area.
+2. `ComposeState.preview` is reset to `ComposePreview.NoPreview`.
+3. No API call is needed since the file was not yet sent.
+
+---
+
+## 6. File Cleanup
+
+1. Files pending deletion are tracked in `ChatModel.filesToDelete`.
+2. When a chat item with a file is deleted, the file path is added to `filesToDelete`.
+3. The actual file deletion happens asynchronously.
+4. Encrypted files require no special cleanup beyond deleting the encrypted file; the key exists only in the database record.
+
+---
+
+## Key Types Reference
+
+| Type | Location | Purpose |
+|------|----------|---------|
+| `CryptoFile` | `model/ChatModel.kt` | File reference with path and optional encryption args |
+| `CryptoFileArgs` | `model/ChatModel.kt` | AES key + nonce for encrypted files |
+| `WriteFileResult` | `model/CryptoFile.kt` | Result of `writeCryptoFile`: success with args or error |
+| `CIFile` | `model/ChatModel.kt` | Chat item file metadata: fileId, fileName, fileSize, fileStatus, fileProtocol |
+| `CIFileStatus` | `model/ChatModel.kt` | File transfer status: SndStored, SndTransfer, SndComplete, RcvInvitation, RcvAccepted, RcvTransfer, RcvComplete, etc. |
+| `FileProtocol` | `model/ChatModel.kt` | Transfer protocol: XFTP, SMP, LOCAL |
+| `FileTransferMeta` | `model/ChatModel.kt` | Metadata for standalone XFTP uploads |
+| `ComposePreview.FilePreview` | `views/chat/ComposeView.kt` | Compose state for file attachment |
diff --git a/apps/multiplatform/product/flows/group-lifecycle.md b/apps/multiplatform/product/flows/group-lifecycle.md
new file mode 100644
index 0000000000..60311f7b47
--- /dev/null
+++ b/apps/multiplatform/product/flows/group-lifecycle.md
@@ -0,0 +1,283 @@
+# Group Lifecycle Flow
+
+> **Related spec:** [spec/api.md](../../spec/api.md) | [spec/client/chat-view.md](../../spec/client/chat-view.md)
+
+## Overview
+
+Groups in SimpleX Chat are decentralized: there is no central group server. The group owner's device coordinates membership, and messages are delivered via pairwise SMP connections between members. Groups support roles, invitation links, member admission review, blocking, and profile updates.
+
+## Prerequisites
+
+- Chat is initialized and running.
+- An active user profile exists.
+- For creating a group: no special requirements.
+- For joining: a group invitation link or a direct invitation from an existing member.
+
+---
+
+## 1. Creating a Group
+
+### 1.1 Create Group
+
+1. User navigates to "New Chat" and selects "Create Group".
+2. The `AddGroupView` collects a group profile: display name, full name, optional image, and optional description.
+3. `ChatController.apiNewGroup(rh, incognito, groupProfile)` is called:
+
+```kotlin
+suspend fun apiNewGroup(rh: Long?, incognito: Boolean, groupProfile: GroupProfile): GroupInfo?
+```
+
+4. `CC.ApiNewGroup(userId, incognito, groupProfile)` is sent to the core.
+5. The core creates the group and returns `CR.GroupCreated` with a `GroupInfo` object.
+6. The creating user is automatically assigned the `Owner` role.
+7. The new group appears in the chat list.
+8. If `incognito = true`, a random profile is used for the user within this group.
+
+### 1.2 Update Group Profile
+
+```kotlin
+suspend fun apiUpdateGroup(rh: Long?, groupId: Long, groupProfile: GroupProfile): GroupInfo?
+```
+
+1. Owner or Admin navigates to group info and edits the profile.
+2. `CC.ApiUpdateGroupProfile(groupId, groupProfile)` is sent to the core.
+3. On success, `CR.GroupUpdated` returns the updated `GroupInfo` with `toGroup`.
+4. The chat model is updated via `chatModel.chatsContext.updateGroup(rh, groupInfo)`.
+5. Profile changes are propagated to all connected members.
+
+---
+
+## 2. Adding Members
+
+### 2.1 Invite a Contact
+
+1. Owner or Admin opens group info and taps "Add Members".
+2. `AddGroupMembersView` displays the user's contacts eligible for invitation.
+3. A role is selected for the invitee (default: `Member`).
+4. `ChatController.apiAddMember(rh, groupId, contactId, memberRole)` is called:
+
+```kotlin
+suspend fun apiAddMember(rh: Long?, groupId: Long, contactId: Long, memberRole: GroupMemberRole): GroupMember?
+```
+
+5. `CC.ApiAddMember(groupId, contactId, memberRole)` is sent to the core.
+6. The core sends a group invitation to the contact via their direct SMP connection.
+7. `CR.SentGroupInvitation` returns a `GroupMember` in `Invited` status.
+8. The member list updates to show the pending invitation.
+
+### 2.2 Invitee Joins
+
+1. The invited contact receives a group invitation event.
+2. A group invitation chat item appears in their chat list.
+3. The invitee taps "Join" to accept.
+4. `ChatController.apiJoinGroup(rh, groupId)` is called.
+5. `CC.ApiJoinGroup(groupId)` is sent to the core.
+6. `CR.UserAcceptedGroupSent` confirms the join request was sent.
+7. The owner's/admin's device processes the join and establishes pairwise connections with existing members.
+8. `CR.MemberConnected` events fire as connections to each member are established.
+
+---
+
+## 3. Member Roles
+
+### 3.1 Role Hierarchy
+
+```kotlin
+enum class GroupMemberRole(val memberRole: String) {
+ Observer("observer"), // Can only read messages
+ Author("author"), // Can send messages but limited
+ Member("member"), // Standard member
+ Moderator("moderator"), // Can moderate content
+ Admin("admin"), // Can manage members
+ Owner("owner") // Full control, can delete group
+}
+```
+
+Selectable roles for assignment: `Observer`, `Member`, `Moderator`, `Admin`, `Owner`.
+
+### 3.2 Change Member Role
+
+```kotlin
+suspend fun apiMembersRole(rh: Long?, groupId: Long, memberIds: List, memberRole: GroupMemberRole): List
+```
+
+1. Owner or Admin navigates to member info in `GroupMemberInfoView`.
+2. Selects a new role from the role picker.
+3. `CC.ApiMembersRole(groupId, memberIds, memberRole)` is sent to the core.
+4. The core responds with `CR.MembersRoleUser` returning updated `GroupMember` objects.
+5. The change is propagated to all group members.
+6. Supports batch role changes (multiple `memberIds`).
+
+---
+
+## 4. Removing and Blocking Members
+
+### 4.1 Remove Members
+
+```kotlin
+suspend fun apiRemoveMembers(rh: Long?, groupId: Long, memberIds: List, withMessages: Boolean): Pair>?
+```
+
+1. Owner or Admin selects a member and taps "Remove".
+2. `CC.ApiRemoveMembers(groupId, memberIds, withMessages)` is sent.
+3. If `withMessages = true`, the removed member's messages are also deleted from all members.
+4. `CR.UserDeletedMembers` returns the updated `GroupInfo` and removed `GroupMember` list.
+5. The removed member receives a notification and loses access to the group.
+
+### 4.2 Block Members for All
+
+```kotlin
+suspend fun apiBlockMembersForAll(rh: Long?, groupId: Long, memberIds: List, blocked: Boolean): List
+```
+
+1. Owner, Admin, or Moderator selects a member and taps "Block for all".
+2. `CC.ApiBlockMembersForAll(groupId, memberIds, blocked)` is sent.
+3. `blocked = true` blocks; `blocked = false` unblocks.
+4. `CR.MembersBlockedForAllUser` returns the updated member list.
+5. Blocked members' messages are hidden from all group members.
+6. The blocked member can still see the group but their messages are not delivered.
+
+---
+
+## 5. Group Links
+
+### 5.1 Create Group Link
+
+```kotlin
+suspend fun apiCreateGroupLink(rh: Long?, groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): GroupLink?
+```
+
+1. Owner or Admin navigates to group info and taps "Create Group Link".
+2. `CC.APICreateGroupLink(groupId, memberRole)` is sent.
+3. A default role for joiners is specified (default: `Member`).
+4. `CR.GroupLinkCreated` returns a `GroupLink` containing the link URI.
+5. The link is displayed in `GroupLinkView` as a QR code and copyable text.
+
+### 5.2 Update Group Link Role
+
+```kotlin
+suspend fun apiGroupLinkMemberRole(rh: Long?, groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): GroupLink?
+```
+
+1. Owner or Admin changes the default role for new members joining via link.
+2. `CC.APIGroupLinkMemberRole(groupId, memberRole)` is sent.
+3. `CR.CRGroupLink` returns the updated link with the new default role.
+
+### 5.3 Get Group Link
+
+```kotlin
+suspend fun apiGetGroupLink(rh: Long?, groupId: Long): GroupLink?
+```
+
+1. Retrieves the existing group link for display.
+2. `CC.APIGetGroupLink(groupId)` is sent.
+3. Returns `null` if no link exists.
+
+### 5.4 Delete Group Link
+
+```kotlin
+suspend fun apiDeleteGroupLink(rh: Long?, groupId: Long): Boolean
+```
+
+1. Owner or Admin navigates to group link settings and taps "Delete Link".
+2. `CC.APIDeleteGroupLink(groupId)` is sent.
+3. `CR.GroupLinkDeleted` confirms deletion.
+4. The link becomes invalid; anyone with the old link can no longer join.
+
+---
+
+## 6. Member Admission Workflow
+
+### 6.1 Admission Configuration
+
+Group owners can require review of new members before they are fully admitted:
+
+```kotlin
+data class GroupMemberAdmission(
+ val review: MemberCriteria? = null
+)
+
+enum class MemberCriteria {
+ All // All joining members require review
+}
+```
+
+1. Owner opens group info and navigates to "Member Admission" (`MemberAdmissionView`).
+2. The `review` field is set to `MemberCriteria.All` to require review of all new members.
+3. The admission configuration is saved by updating the group profile:
+ - `groupProfile.copy(memberAdmission = admission)` is passed to `apiUpdateGroup`.
+4. Changes are tracked with unsaved-changes detection (save/discard prompt on navigation).
+
+### 6.2 Accept a Pending Member
+
+```kotlin
+suspend fun apiAcceptMember(rh: Long?, groupId: Long, groupMemberId: Long, memberRole: GroupMemberRole): Pair?
+```
+
+1. When admission review is enabled, new members joining via link arrive in a pending state.
+2. Owner or Admin sees pending members in the member support chat / member list.
+3. User selects "Accept" and optionally adjusts the role.
+4. `CC.ApiAcceptMember(groupId, groupMemberId, memberRole)` is sent.
+5. `CR.MemberAccepted` returns the updated `GroupInfo` and accepted `GroupMember`.
+6. The member is now fully connected and can participate in the group.
+
+### 6.3 Reject a Pending Member
+
+1. Owner or Admin selects "Reject" on a pending member.
+2. The member is removed via `apiRemoveMembers`.
+3. The rejected member receives a removal notification.
+
+---
+
+## 7. Leaving a Group
+
+```kotlin
+suspend fun apiLeaveGroup(rh: Long?, groupId: Long): GroupInfo?
+```
+
+1. User navigates to group info and taps "Leave Group".
+2. A confirmation dialog is shown.
+3. `CC.ApiLeaveGroup(groupId)` is sent to the core.
+4. `CR.LeftMemberUser` returns the updated `GroupInfo`.
+5. The user's membership status changes and they can no longer send or receive messages.
+6. The group remains in the chat list in a "left" state, and can be deleted locally.
+
+---
+
+## 8. Listing Members
+
+```kotlin
+suspend fun apiListMembers(rh: Long?, groupId: Long): List
+```
+
+1. When opening group info or the member list, `apiListMembers` is called.
+2. `CC.ApiListMembers(groupId)` is sent to the core.
+3. `CR.GroupMembers` returns the member list.
+4. `ChatModel.groupMembers` and `ChatModel.groupMembersIndexes` are updated.
+5. `ChatModel.membersLoaded` is set to `true`.
+
+---
+
+## 9. Group Chat Scope (Support Channels)
+
+Groups support scoped conversations for member support:
+
+- `GroupChatScope` parameter on message APIs allows sending messages within a specific scope (e.g., member support chat).
+- `MemberSupportChatView` and `MemberSupportView` provide UI for admin-to-member private conversations within the group context.
+- `GroupReportsView` shows moderation reports scoped to the group.
+
+---
+
+## Key Types Reference
+
+| Type | Location | Purpose |
+|------|----------|---------|
+| `GroupInfo` | `model/ChatModel.kt` | Group metadata: groupId, groupProfile, membership, fullGroupPreferences |
+| `GroupProfile` | `model/ChatModel.kt` | Group display info: displayName, fullName, description, image, memberAdmission |
+| `GroupMember` | `model/ChatModel.kt` | Member info: groupMemberId, memberRole, memberStatus, memberProfile |
+| `GroupMemberRole` | `model/ChatModel.kt` | Enum: Observer, Author, Member, Moderator, Admin, Owner |
+| `GroupMemberAdmission` | `model/ChatModel.kt` | Admission settings: review criteria |
+| `MemberCriteria` | `model/ChatModel.kt` | Enum: All (require review for all) |
+| `GroupLink` | `model/SimpleXAPI.kt` | Group link: connLinkContact, acceptMemberRole, userContactLinkId, shortLinkDataSet, shortLinkLargeDataSet, groupLinkId |
+| `GroupChatScope` | `model/ChatModel.kt` | Scoped conversation within a group |
+| `ConnectionPlan.GroupLink` | `model/SimpleXAPI.kt` | Plan result when connecting via a group link |
diff --git a/apps/multiplatform/product/flows/messaging.md b/apps/multiplatform/product/flows/messaging.md
new file mode 100644
index 0000000000..771eae1c4e
--- /dev/null
+++ b/apps/multiplatform/product/flows/messaging.md
@@ -0,0 +1,195 @@
+# Messaging Flow
+
+> **Related spec:** [spec/client/compose.md](../../spec/client/compose.md) | [spec/api.md](../../spec/api.md)
+
+## Overview
+
+Messaging is the core interaction in SimpleX Chat. Users compose and send text, images, video, voice notes, files, and link previews. Messages can be replied to, edited, deleted, forwarded, and reacted to with emoji. Special modes include timed (disappearing) messages, live messages (real-time typing), and message reports for moderation.
+
+All message operations flow through the Haskell core via `ChatController.apiSendMessages`, with responses updating `ChatModel` and triggering Compose UI recomposition.
+
+## Prerequisites
+
+- Chat is initialized and running (`ChatModel.chatRunning == true`).
+- An active user exists (`ChatModel.currentUser != null`).
+- A chat is open (`ChatModel.chatId != null`) with an established connection.
+
+---
+
+## 1. Sending a Text Message
+
+### 1.1 Compose and Send
+
+1. User types in the compose field. `ComposeState.message` is updated as a `ComposeMessage(text, selection)`.
+2. The compose area tracks context via `ComposeContextItem`: `NoContextItem` for a fresh message, `QuotedItem` for a reply, `EditingItem` for an edit, `ForwardingItems` for forwarding, or `ReportedItem` for a report.
+3. User taps the send button. The `ComposeView` builds a `ComposedMessage`:
+ ```kotlin
+ class ComposedMessage(
+ val fileSource: CryptoFile?,
+ val quotedItemId: Long?,
+ val msgContent: MsgContent,
+ val mentions: Map
+ )
+ ```
+4. For plain text, `msgContent` is `MsgContent.MCText(text)`.
+5. `ChatController.apiSendMessages(rh, type, id, scope, live, ttl, composedMessages)` is called.
+6. The core command `CC.ApiSendMessages` is dispatched via `sendCmd`.
+7. On success, the response `CR.NewChatItems` returns a list of `AChatItem`.
+8. `ChatModel` is updated and the chat item list recomposes to show the new message.
+9. `ComposeState` is reset to its default.
+
+### 1.2 Link Preview
+
+1. As the user types, the text is parsed for URLs.
+2. If `privacyLinkPreviews` preference is enabled and a URL is detected, a `LinkPreview` is fetched asynchronously.
+3. The compose preview is set to `ComposePreview.CLinkPreview(linkPreview)`.
+4. When sent, the `msgContent` is `MsgContent.MCLink(text, preview)`.
+
+---
+
+## 2. Sending Media (Image, Video, Voice)
+
+### 2.1 Image
+
+1. User picks or captures an image.
+2. The image is resized (max inline data size `MAX_IMAGE_SIZE` = 255 KB for the preview thumbnail).
+3. The full-size file is saved to the app files directory.
+4. If local file encryption is enabled (`privacyEncryptLocalFiles`), the file is encrypted via `encryptCryptoFile`, producing a `CryptoFile` with `CryptoFileArgs(fileKey, fileNonce)`.
+5. Compose preview becomes `ComposePreview.MediaPreview(images, content)`.
+6. On send, `msgContent` is `MsgContent.MCImage(text, imageBase64)` and `fileSource` is the `CryptoFile`.
+7. The core handles inline delivery (for small files) or XFTP upload (for larger files).
+
+### 2.2 Video
+
+1. User picks or records a video.
+2. A thumbnail image is extracted and resized.
+3. The video file is saved and optionally encrypted.
+4. On send, `msgContent` is `MsgContent.MCVideo(text, image, duration)`.
+
+### 2.3 Voice Message
+
+1. User records a voice note. Recording state is tracked via `RecordingState` (NotStarted, Started, Finished).
+2. The compose preview becomes `ComposePreview.VoicePreview(voice, durationMs, finished)`.
+3. On send, `msgContent` is `MsgContent.MCVoice(text, durationSeconds)`.
+4. A file attachment carries the actual audio data.
+
+---
+
+## 3. Sending Files
+
+1. User picks a file via the file chooser.
+2. File size is validated against `MAX_FILE_SIZE_XFTP` (1 GB).
+3. Compose preview becomes `ComposePreview.FilePreview(fileName, uri)`.
+4. On send, `msgContent` is `MsgContent.MCFile(text)` and the `fileSource` is populated.
+5. Delivery via inline (small files under SMP threshold) or XFTP (large files) is determined by the core.
+
+---
+
+## 4. Receiving Messages
+
+1. The `ChatController` receiver loop calls `chatRecvMsgWait` on the Haskell core.
+2. Incoming messages arrive as `CR.NewChatItems` events.
+3. `ChatModel` chat items list is updated, triggering recomposition.
+4. For media messages, images below `MAX_IMAGE_SIZE_AUTO_RCV` (510 KB), videos below `MAX_VIDEO_SIZE_AUTO_RCV` (1023 KB), and voice notes below `MAX_VOICE_SIZE_AUTO_RCV` (510 KB) are auto-received if `privacyAcceptImages` is enabled.
+5. Larger files require manual download initiation (see File Transfer Flow).
+
+---
+
+## 5. Editing a Message
+
+1. User long-presses a sent message and selects "Edit".
+2. `ComposeContextItem` becomes `EditingItem(chatItem)`.
+3. The original text populates the compose field.
+4. On send, `ChatController.apiUpdateChatItem(rh, type, id, scope, itemId, updatedMessage, live)` is called.
+5. `updatedMessage` is an `UpdatedMessage(msgContent, mentions)`.
+6. The core responds with `CR.ChatItemUpdated` or `CR.ChatItemNotChanged`.
+7. The chat item in `ChatModel` is updated in place.
+
+---
+
+## 6. Deleting a Message
+
+1. User long-presses a message and selects "Delete".
+2. A delete mode is chosen: `CIDeleteMode.cidmBroadcast` (delete for everyone), `CIDeleteMode.cidmInternal` (delete for self), or `CIDeleteMode.cidmInternalMark` (mark as deleted internally).
+3. `ChatController.apiDeleteChatItems(rh, type, id, scope, itemIds, mode)` is called.
+4. The core responds with `CR.ChatItemsDeleted`, returning a list of `ChatItemDeletion`.
+5. For group chats by moderators, `apiDeleteMemberChatItems(rh, groupId, itemIds)` is used.
+6. Deleted items are either removed from the UI or replaced with a "deleted" marker.
+
+---
+
+## 7. Reacting to a Message
+
+1. User long-presses a message and selects an emoji reaction.
+2. `ChatController.apiChatItemReaction(rh, type, id, scope, itemId, add, reaction)` is called.
+3. `reaction` is a `MsgReaction` (typically emoji).
+4. `add = true` to add, `add = false` to remove a reaction.
+5. The core responds with `CR.ChatItemReaction`, and the chat item's reaction list is updated.
+6. In groups, `apiGetReactionMembers` can be called to see who reacted.
+
+---
+
+## 8. Replying to a Message
+
+1. User swipes or long-presses a message and selects "Reply".
+2. `ComposeContextItem` becomes `QuotedItem(chatItem)`.
+3. The quoted item preview is shown above the compose field.
+4. On send, the `ComposedMessage.quotedItemId` is set to the quoted item's ID.
+5. The sent message renders with the quoted content inline.
+
+---
+
+## 9. Forwarding Messages
+
+1. User selects one or more messages and taps "Forward".
+2. `ChatController.apiPlanForwardChatItems(rh, fromChatType, fromChatId, fromScope, chatItemIds)` is called first to get a `CR.ForwardPlan` with forwardable/non-forwardable item categorization.
+3. `ComposeContextItem` becomes `ForwardingItems(chatItems, fromChatInfo)`.
+4. User picks a destination chat.
+5. `ChatController.apiForwardChatItems(rh, toChatType, toChatId, toScope, fromChatType, fromChatId, fromScope, itemIds, ttl)` is called.
+6. New chat items are created in the destination chat.
+
+---
+
+## 10. Timed (Disappearing) Messages
+
+1. Timed messages are enabled per-chat via chat feature preferences.
+2. When composing, a TTL (time-to-live) in seconds is passed as the `ttl` parameter to `apiSendMessages`.
+3. The core attaches the TTL to the message metadata.
+4. After the TTL expires, the message is automatically deleted on both sides.
+5. The UI shows a countdown indicator on timed messages via `CIMetaView`.
+
+---
+
+## 11. Live Messages
+
+1. User enables live message mode (long-press on send button if `liveMessageAlertShown` preference allows).
+2. `ComposeState.liveMessage` is set to a `LiveMessage(chatItem, typedMsg, sentMsg, sent)`.
+3. As the user types, `apiSendMessages` is called with `live = true` for the initial send, then `apiUpdateChatItem` with `live = true` for subsequent updates.
+4. The recipient sees the message content updating in real-time.
+5. When the user finalizes (taps send), a final `apiUpdateChatItem` with `live = false` is sent.
+
+---
+
+## 12. Message Reports
+
+1. User long-presses a message and selects "Report".
+2. `ComposeContextItem` becomes `ReportedItem(chatItem, reason)` where `reason` is a `ReportReason`.
+3. On send, `msgContent` is `MsgContent.MCReport(text, reason)`.
+4. The report is sent to group owners/admins for moderation review.
+5. Group admins see reports in the `GroupReportsView`.
+
+---
+
+## Key Types Reference
+
+| Type | Location | Purpose |
+|------|----------|---------|
+| `ComposeState` | `views/chat/ComposeView.kt` | Tracks compose field state |
+| `ComposePreview` | `views/chat/ComposeView.kt` | Preview type: NoPreview, CLinkPreview, MediaPreview, VoicePreview, FilePreview |
+| `ComposeContextItem` | `views/chat/ComposeView.kt` | Context: NoContextItem, QuotedItem, EditingItem, ForwardingItems, ReportedItem |
+| `ComposedMessage` | `model/SimpleXAPI.kt` | Wire format for sending: fileSource, quotedItemId, msgContent, mentions |
+| `UpdatedMessage` | `model/SimpleXAPI.kt` | Wire format for editing: msgContent, mentions |
+| `MsgContent` | `model/ChatModel.kt` | Sealed class: MCText, MCLink, MCImage, MCVideo, MCVoice, MCFile, MCReport, MCChat, MCUnknown |
+| `LiveMessage` | `views/chat/ComposeView.kt` | Tracks live message state |
+| `MsgReaction` | `model/ChatModel.kt` | Emoji reaction type |
+| `ChatItemDeletion` | `model/ChatModel.kt` | Deletion result with old/new item |
diff --git a/apps/multiplatform/product/flows/onboarding.md b/apps/multiplatform/product/flows/onboarding.md
new file mode 100644
index 0000000000..b6b3e835a5
--- /dev/null
+++ b/apps/multiplatform/product/flows/onboarding.md
@@ -0,0 +1,205 @@
+# Onboarding Flow
+
+> **Related spec:** [spec/client/navigation.md](../../spec/client/navigation.md) | [spec/architecture.md](../../spec/architecture.md)
+
+## Overview
+
+Onboarding is the first-run experience that initializes the Haskell chat core, creates the local database, sets up the user profile, configures server operators, and (on Android) selects the notification mode. The flow is tracked by the `OnboardingStage` enum persisted in `AppPreferences.onboardingStage`.
+
+The initialization path differs slightly between Android and Desktop, but both converge on the common `chatMigrateInit` JNI call and shared `ChatController` logic.
+
+## Prerequisites
+
+- Fresh install or database reset.
+- On Android: `SimplexApp.onCreate()` has been called.
+- On Desktop: `main()` has been called.
+
+---
+
+## 1. Platform Initialization
+
+### 1.1 Android: SimplexApp.onCreate()
+
+1. `SimplexApp.onCreate()` is called by the Android framework.
+2. `AppContextProvider.initialize(this)` sets the application context.
+3. Phoenix process detection: if this is a restart process, return early.
+4. A global error handler is registered.
+5. `initHaskell(packageName)` loads the native `libapp-lib.so` and calls `initHS()` to initialize the Haskell runtime.
+6. `initMultiplatform()` sets up:
+ - `androidAppContext` reference.
+ - `ntfManager` (notification manager bridge to Android `NtfManager`).
+ - `platform` interface implementation with Android-specific callbacks for services, notifications, call management, and UI configuration.
+7. `reconfigureBroadcastReceivers()` ensures notification-related receivers match saved preferences.
+8. `runMigrations()` performs any pending app-level data migrations.
+9. Temp directory is cleaned and recreated.
+10. If a migration state exists (`chatModel.migrationState.value != null`), onboarding is forced to `Step1_SimpleXInfo`.
+11. Otherwise, if authentication keys are available, `initChatControllerOnStart()` is called.
+
+### 1.2 Desktop: Main.kt main()
+
+1. `initHaskell()` loads native libraries:
+ - On Linux/macOS: `libapp-lib.so` / `libapp-lib.dylib`.
+ - On Windows: `libcrypto-3-x64.dll`, `libsimplex.dll`, `libapp-lib.dll` plus VLC libraries.
+2. `initHS()` initializes the Haskell runtime.
+3. `platform` interface is set with Desktop-specific callbacks (app update notice).
+4. `runMigrations()` performs pending app-level data migrations.
+5. `setupUpdateChecker()` configures the desktop update channel.
+6. `initApp()` initializes common app state.
+7. Temp directory is cleaned and recreated.
+8. `showApp()` launches the Compose Desktop window, which renders the `AppView`.
+
+---
+
+## 2. Database Initialization (chatMigrateInit)
+
+### 2.1 initChatController
+
+1. `initChatController(useKey, confirmMigrations, startChat)` is called (from `Core.kt`).
+2. If `ctrlInitInProgress` is already true, return (prevents double initialization).
+3. The database key is resolved:
+ - From `useKey` parameter if provided.
+ - Otherwise from `DatabaseUtils.useDatabaseKey()` which reads from the keystore.
+4. Migration confirmation mode is determined:
+ - `MigrationConfirmation.YesUp` (auto-confirm forward migrations) by default.
+ - `MigrationConfirmation.Error` if developer tools + confirm upgrades are enabled.
+5. `chatMigrateInit(dbPath, dbKey, confirm)` is called via JNI. This:
+ - Opens (or creates) the SQLite database at `dbAbsolutePrefixPath`.
+ - Runs all pending schema migrations.
+ - Returns a `ChatCtrl` handle (Long) and a `DBMigrationResult`.
+6. On `DBMigrationResult.OK`:
+ - The `ChatCtrl` is stored globally.
+ - `ChatModel.chatDbStatus` is set.
+ - App file paths are configured via `apiSetAppFilePaths`.
+ - `apiGetActiveUser` checks for an existing user.
+7. If an active user exists, `startChat(user)` is called.
+8. If no user exists, `startChatWithoutUser()` is called and onboarding begins at `Step1_SimpleXInfo`.
+
+### 2.2 Error Handling
+
+- `DBMigrationResult.ErrorNotADatabase`: Wrong passphrase or corrupted DB. User is prompted.
+- `DBMigrationResult.ErrorMigration`: Migration failed. Details shown to user.
+- `DBMigrationResult.ErrorKeyNotSet`: Encryption key missing.
+- `DBMigrationResult.InvalidConfirmation`: Migrations need manual confirmation (developer mode).
+- On any error, `ChatModel.chatDbStatus` is set and the UI shows the appropriate database error screen.
+
+---
+
+## 3. Onboarding Stages
+
+The onboarding flow is controlled by `OnboardingStage`, persisted in `AppPreferences.onboardingStage`:
+
+```kotlin
+enum class OnboardingStage {
+ Step1_SimpleXInfo,
+ Step2_CreateProfile,
+ LinkAMobile,
+ Step2_5_SetupDatabasePassphrase,
+ Step3_ChooseServerOperators,
+ Step3_CreateSimpleXAddress,
+ Step4_SetNotificationsMode,
+ OnboardingComplete
+}
+```
+
+### 3.1 Step1_SimpleXInfo
+
+1. The `SimpleXInfo` screen is shown.
+2. Explains what SimpleX Chat is: privacy, no user identifiers, decentralized.
+3. User taps "Create your profile" to proceed.
+4. On Desktop, a "Link a Mobile" option is also available.
+
+### 3.2 Step2_CreateProfile
+
+1. The `CreateProfile` screen is shown.
+2. User enters a display name (validated via `chatValidName` JNI) and optional full name.
+3. On submit, `ChatController.apiCreateActiveUser(rh, profile)` is called:
+ ```kotlin
+ suspend fun apiCreateActiveUser(rh: Long?, p: Profile?, pastTimestamp: Boolean = false, ctrl: ChatCtrl? = null): User?
+ ```
+4. The core command `CC.CreateActiveUser(p, pastTimestamp)` creates the user in the database.
+5. On success, `CR.ActiveUser` returns the new `User` object.
+6. `ChatModel.currentUser` is set.
+7. If the chat is not yet running, `startChat(user)` is called:
+ - `apiSetNetworkConfig` configures network settings.
+ - `apiStartChat` starts the message receiver.
+ - `startReceiver()` begins polling for incoming messages.
+8. Onboarding advances to `Step3_ChooseServerOperators`.
+
+### 3.3 LinkAMobile (Desktop Only)
+
+1. Available as an alternative to creating a profile on Desktop.
+2. Shows a QR code for linking with a mobile device.
+3. The desktop acts as a remote host controlled by the mobile app.
+
+### 3.4 Step2_5_SetupDatabasePassphrase (Desktop Only)
+
+1. On Desktop, after profile creation, the user is optionally prompted to set a database passphrase.
+2. If skipped, a random passphrase is used (`desktopOnboardingRandomPassword` flag).
+3. `ChatModel.desktopOnboardingRandomPassword` tracks this state.
+
+### 3.5 Step3_ChooseServerOperators
+
+1. The `ChooseServerOperators` screen is shown.
+2. User selects which preset server operators to use for messaging and file transfer.
+3. Server operator conditions may need to be accepted.
+4. The selection is saved via the server configuration APIs.
+
+### 3.6 Step3_CreateSimpleXAddress
+
+1. User is prompted to create a SimpleX address for receiving contact requests.
+2. This calls the address creation API.
+3. Can be skipped.
+
+### 3.7 Step4_SetNotificationsMode (Android Only)
+
+1. The `SetNotificationsMode` screen is shown.
+2. Three modes are available:
+ - `NotificationsMode.SERVICE`: Persistent background service (instant notifications).
+ - `NotificationsMode.PERIODIC`: Periodic background work (delayed notifications).
+ - `NotificationsMode.OFF`: No background processing (manual check only).
+3. On selection, `appPrefs.notificationsMode` is set.
+4. On Desktop, this step is skipped entirely.
+
+### 3.8 OnboardingComplete
+
+1. `appPrefs.onboardingStage` is set to `OnboardingComplete`.
+2. The chat list view (`ChatListView`) is shown.
+3. On Android, `SimplexService.showBackgroundServiceNoticeIfNeeded()` may show additional setup prompts.
+4. On Android with `NotificationsMode.SERVICE`, `SimplexService.start()` is called.
+
+---
+
+## 4. startChat Flow
+
+After the user is created and onboarding progresses, `ChatController.startChat(user)` orchestrates the final setup:
+
+1. `apiSetNetworkConfig(getNetCfg())` applies network configuration.
+2. `apiCheckChatRunning()` checks if the core is already running.
+3. `listUsers(null)` loads all user profiles into `ChatModel.users`.
+4. If chat is not running:
+ - `ChatModel.currentUser` is set.
+ - `apiStartChat()` starts the core's message processing.
+ - `startReceiver()` begins the message receive loop.
+ - `setLocalDeviceName` sets the device name for remote access.
+5. `apiGetChats` loads the chat list.
+6. `chatModel.chatsContext.updateChats(chats)` populates the UI.
+7. User address and chat item TTL are loaded.
+8. `appPrefs.chatLastStart` is updated.
+9. `ChatModel.chatRunning` is set to `true`.
+10. `platform.androidChatInitializedAndStarted()` is called for Android-specific post-start tasks.
+
+---
+
+## Key Types Reference
+
+| Type | Location | Purpose |
+|------|----------|---------|
+| `OnboardingStage` | `views/onboarding/OnboardingView.kt` | Enum tracking onboarding progress |
+| `SimplexApp` | `android/.../SimplexApp.kt` | Android Application class, entry point |
+| `Main.kt` | `desktop/.../Main.kt` | Desktop entry point |
+| `ChatController` | `model/SimpleXAPI.kt` | Core API controller, manages chat lifecycle |
+| `ChatModel` | `model/ChatModel.kt` | Global observable state |
+| `DBMigrationResult` | `views/helpers/DatabaseUtils.kt` | Database migration outcome |
+| `chatMigrateInit` | `platform/Core.kt` | JNI function: initialize DB and run migrations |
+| `initChatController` | `platform/Core.kt` | High-level initialization orchestrator |
+| `AppPreferences` | `model/SimpleXAPI.kt` | Persistent user preferences |
diff --git a/apps/multiplatform/product/gaps.md b/apps/multiplatform/product/gaps.md
new file mode 100644
index 0000000000..25535d8003
--- /dev/null
+++ b/apps/multiplatform/product/gaps.md
@@ -0,0 +1,290 @@
+# Known Gaps & Recommendations -- SimpleX Chat (Android & Desktop, Kotlin Multiplatform)
+
+This document catalogs known gaps in the multiplatform codebase (Android and Desktop) with severity, impact, and recommendations.
+
+---
+
+## Table of Contents
+
+1. [UI: Error Feedback](#gap-01-ui-error-feedback)
+2. [UI: Loading States](#gap-02-ui-loading-states)
+3. [Security: Database Passphrase Not Enforced](#gap-03-security-database-passphrase-not-enforced)
+4. [Security: No Forward Secrecy Indicator](#gap-04-security-no-forward-secrecy-indicator)
+5. [Documentation: Haskell Store Layer Not Fully Specified](#gap-05-documentation-haskell-store-layer-not-fully-specified)
+6. [Desktop: Recording Not Implemented](#gap-06-desktop-recording-not-implemented)
+7. [Desktop: Cryptor Not Implemented](#gap-07-desktop-cryptor-not-implemented)
+
+---
+
+## GAP-01: UI Error Feedback
+
+**Severity:** Medium
+**Category:** UI / UX
+**Platforms:** Android, Desktop
+
+### Description
+
+Many API calls through `ChatController.sendCmd()` return `API.Error` responses that are logged but not surfaced to the user. The general pattern is:
+
+```kotlin
+val r = sendCmd(rh, cmd)
+if (r is API.Result && r.res is CR.ExpectedResponse) return r.res.value
+Log.e(TAG, "someFunction bad response: ${r.responseType} ${r.details}")
+return null
+```
+
+When the call fails, the caller receives `null` and either silently does nothing or shows a generic error. The specific `ChatError` details (which may contain actionable information like quota exceeded, server unreachable, or store errors) are lost to the user.
+
+### Affected Locations
+
+- `SimpleXAPI.kt` -- `getAgentSubsTotal()`, `getAgentServersSummary()`, and dozens of similar `api*` functions
+- Throughout the codebase wherever `sendCmd` results are pattern-matched
+
+### Impact
+
+Users experience silent failures with no indication of what went wrong. This is particularly problematic for:
+- Connection attempts that fail due to network issues
+- File transfer failures
+- Group operations that fail due to role permissions
+- Server configuration errors
+
+### Recommendation
+
+1. Introduce a structured error-handling utility that maps `ChatError` subtypes to user-visible messages, similar to how `retryableNetworkErrorAlert` already handles a subset of `AgentErrorType.BROKER` errors.
+2. At minimum, surface a dismissible snackbar/toast with a summary when an API call fails unexpectedly.
+3. For critical operations (send message, join group, create connection), show a dialog with retry/cancel options (the `sendCmdWithRetry` pattern already exists for some cases -- extend it).
+
+---
+
+## GAP-02: UI Loading States
+
+**Severity:** Low-Medium
+**Category:** UI / UX
+**Platforms:** Android, Desktop
+
+### Description
+
+Several long-running operations lack loading indicators, leaving the user uncertain whether the action is in progress. The `ComposeState.inProgress` flag and `progressByTimeout` mechanism exist for the compose area, and `ConnectProgressManager` handles connection progress, but many other flows have no visual feedback.
+
+### Affected Locations
+
+- Group member list loading (`ChatModel.membersLoaded` exists but is not always checked before displaying stale data)
+- Server configuration validation (`ApiValidateServers` can take several seconds with no indicator)
+- Database export/import (`ApiExportArchive`, `ApiImportArchive`)
+- Profile switching (`changeActiveUser_` acquires `changingActiveUserMutex` but the UI may appear frozen)
+
+### Impact
+
+Users may tap actions multiple times, causing duplicate requests, or assume the app is frozen and force-quit during a long operation like database export.
+
+### Recommendation
+
+1. Introduce a centralized `ProgressOverlay` composable that can be shown/hidden via a `ChatModel` flag.
+2. Wrap all operations that acquire `changingActiveUserMutex` or take > 1 second with a visible loading state.
+3. Use `ChatModel.switchingUsersAndHosts` (which already exists) more consistently as a gate for showing a blocking progress indicator.
+
+---
+
+## GAP-03: Security: Database Passphrase Not Enforced
+
+**Severity:** High
+**Category:** Security
+**Platforms:** Android, Desktop
+
+### Description
+
+When the app is first installed, a random database passphrase is generated and stored in encrypted preferences. The user is never required to set a custom passphrase. The `initialRandomDBPassphrase` flag tracks this state, and a setup prompt exists in onboarding (`SetupDatabasePassphrase`), but the user can skip it.
+
+On Android, the encrypted passphrase is stored via the Android Keystore, which provides hardware-backed security. On Desktop, the `Cryptor` is a **placeholder** (see GAP-07), meaning the passphrase is stored in plaintext.
+
+### Affected Locations
+
+- `SimpleXAPI.kt` -- `AppPreferences.storeDBPassphrase`, `AppPreferences.initialRandomDBPassphrase`, `AppPreferences.encryptedDBPassphrase`
+- `common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt`
+- `common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt`
+
+### Impact
+
+- Users who skip passphrase setup rely entirely on device security. If the device is compromised, the database can be decrypted using the stored passphrase.
+- On Desktop, the passphrase is effectively stored in plaintext (see GAP-07), meaning anyone with filesystem access can read the database.
+
+### Recommendation
+
+1. Consider making passphrase setup mandatory during onboarding (or at least prominently warn users who skip it).
+2. On Desktop, implement proper key storage (GAP-07) before any passphrase enforcement is meaningful.
+3. Add a periodic reminder for users who still have `initialRandomDBPassphrase == true`.
+
+---
+
+## GAP-04: Security: No Forward Secrecy Indicator
+
+**Severity:** Medium
+**Category:** Security / UI
+**Platforms:** Android, Desktop
+
+### Description
+
+The double-ratchet algorithm provides forward secrecy per message, and PQ key exchange provides resistance to quantum attacks. The `Connection` type tracks `pqSupport`, `pqEncryption`, `pqSndEnabled`, and `pqRcvEnabled`. However, the UI does not prominently display the current forward secrecy state or PQ encryption status for a given conversation.
+
+### Affected Locations
+
+- `ChatModel.kt` -- `Connection.pqSupport`, `Connection.pqEncryption`, `Connection.pqSndEnabled`, `Connection.pqRcvEnabled`
+- Contact info views, group member info views
+
+### Impact
+
+Users cannot easily verify whether their conversations are using PQ-enhanced encryption. Security-conscious users have no visual indicator of the ratchet state or whether PQ key exchange was successful.
+
+### Recommendation
+
+1. Add a security badge/icon in the chat header or contact info screen showing:
+ - Whether PQ key exchange is active (both peers support it)
+ - Whether the connection has been verified (security code comparison)
+ - The ratchet state (in-sync vs. needs re-sync)
+2. The `connectionCode` field on `Connection` can be used to show verification status.
+3. The `Call.encryptionStatus` pattern (used in call views) could be adapted for the chat view.
+
+---
+
+## GAP-05: Documentation: Haskell Store Layer Not Fully Specified
+
+**Severity:** Medium
+**Category:** Documentation / Architecture
+**Platforms:** Android, Desktop
+
+### Description
+
+The Kotlin client communicates with the Haskell core via a text-based command protocol (`CC.cmdString` -> FFI -> Haskell). The Haskell store layer (SQLite operations, migration logic, and the exact semantics of `StoreError` variants) is not documented from the Kotlin side. The `ChatErrorStore` error type wraps a `StoreError` whose variants are defined in Haskell and deserialized by the Kotlin client, but the conditions under which each error occurs are not specified.
+
+### Affected Locations
+
+- `SimpleXAPI.kt:6986` -- `ChatErrorStore(storeError: StoreError)`
+- `SimpleXAPI.kt` -- `StoreError` sealed class (deserialized from Haskell responses)
+- `SimpleXAPI.kt` -- `ChatErrorDatabase(databaseError: DatabaseError)` for migration errors
+
+### Impact
+
+- Developers cannot predict which `StoreError` will occur for a given operation without reading the Haskell source.
+- Error handling in the Kotlin layer is necessarily generic since the error semantics are not specified.
+- Migration failures (`ChatErrorDatabase`) are particularly opaque.
+
+### Recommendation
+
+1. Create a specification document mapping each `CC` command to its possible `StoreError` / `DatabaseError` responses.
+2. Document the database migration versioning scheme and the conditions under which `confirmDBUpgrades` is triggered.
+3. Add inline documentation to the `StoreError` sealed class variants explaining their trigger conditions.
+
+---
+
+## GAP-06: Desktop: Recording Not Implemented
+
+**Severity:** High
+**Category:** Feature / Platform
+**Platform:** Desktop only
+
+### Description
+
+The `RecorderNative` class on Desktop is a placeholder. Both `start()` and `stop()` are stubbed with `/*LALAL*/` comments and return dummy values (empty string and 0, respectively). Users cannot record voice messages on Desktop.
+
+```kotlin
+// common/src/desktopMain/kotlin/chat/simplex/common/platform/RecAndPlay.desktop.kt
+actual class RecorderNative: RecorderInterface {
+ override fun start(onProgressUpdate: (position: Int?, finished: Boolean) -> Unit): String {
+ /*LALAL*/
+ return ""
+ }
+
+ override fun stop(): Int {
+ /*LALAL*/
+ return 0
+ }
+}
+```
+
+Audio playback IS implemented on Desktop (via VLC/`vlcj` library), so received voice messages can be played. Only recording is missing.
+
+### Affected Locations
+
+- `common/src/desktopMain/kotlin/chat/simplex/common/platform/RecAndPlay.desktop.kt:15-25`
+- `common/src/commonMain/kotlin/chat/simplex/common/platform/RecAndPlay.kt` -- `RecorderInterface`
+
+### Impact
+
+Desktop users cannot send voice messages. The record button either does nothing or produces a zero-length file.
+
+### Recommendation
+
+1. Implement `RecorderNative` using a JVM audio capture library (e.g., `javax.sound.sampled`, or integrate with the existing `vlcj` dependency for capture).
+2. The output format should match the mobile app's voice message format (likely Opus in an OGG container) for cross-platform compatibility.
+3. Until implemented, the record button should be hidden or disabled on Desktop with a tooltip explaining the limitation.
+
+### Additional Desktop LALAL Placeholders
+
+Several other Desktop features are also marked with `LALAL` placeholders:
+- **QR Code Scanner** (`QRCodeScanner.desktop.kt:12`) -- scanning QR codes is not implemented on Desktop
+- **Animated Drawables** (`Utils.desktop.kt:179`) -- animated image support (e.g., GIF in-line rendering) is not implemented
+- **Animated Chat Images** (`CIImageView.desktop.kt:19`) -- animated image rendering in chat items
+- **isImage detection** (`Images.desktop.kt:168`) -- image type detection (implemented but marked as incomplete)
+
+---
+
+## GAP-07: Desktop: Cryptor Not Implemented
+
+**Severity:** Critical
+**Category:** Security / Platform
+**Platform:** Desktop only
+
+### Description
+
+The `CryptorInterface` implementation on Desktop is a non-functional placeholder. All three methods are stubbed:
+
+```kotlin
+// common/src/desktopMain/kotlin/chat/simplex/common/platform/Cryptor.desktop.kt
+actual val cryptor: CryptorInterface = object : CryptorInterface {
+ override fun decryptData(data: ByteArray, iv: ByteArray, alias: String): String? {
+ return String(data) // LALAL
+ }
+
+ override fun encryptText(text: String, alias: String): Pair {
+ return text.toByteArray() to text.toByteArray() // LALAL
+ }
+
+ override fun deleteKey(alias: String) {
+ // LALAL
+ }
+}
+```
+
+- `decryptData` returns the data as-is (no decryption)
+- `encryptText` returns the plaintext as both "encrypted data" and "IV"
+- `deleteKey` is a no-op
+
+### Affected Locations
+
+- `common/src/desktopMain/kotlin/chat/simplex/common/platform/Cryptor.desktop.kt`
+- `common/src/commonMain/kotlin/chat/simplex/common/platform/Cryptor.kt` -- `CryptorInterface`
+- `common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt` -- uses `cryptor` for passphrase encryption
+
+### Impact
+
+**This is a critical security gap.** On Desktop:
+- The database passphrase is stored **in plaintext** in the preferences file. Anyone with read access to the user's home directory can extract the passphrase and decrypt the database.
+- The self-destruct passphrase is similarly stored in plaintext.
+- The app passphrase (for local authentication) provides no real protection.
+- Key deletion is a no-op, so "deleting" a key has no effect.
+
+This directly undermines RULE-02 (Database Encryption at Rest) and RULE-04 (Self-Destruct Profile) on the Desktop platform.
+
+### Recommendation
+
+1. **Priority: Critical.** Implement proper key storage on Desktop using one of:
+ - **OS Keychain integration:** macOS Keychain, Windows Credential Manager, Linux Secret Service (via `libsecret`/GNOME Keyring/KWallet)
+ - **Java Cryptography Architecture (JCA)** with a PKCS#12 keystore file protected by a master password
+ - **Bouncy Castle** library for platform-independent key management
+2. Until a real implementation exists, display a prominent warning to Desktop users that their database passphrase is not securely stored.
+3. Consider requiring the user to enter their passphrase on each app launch (do not store it) as an interim measure.
+
+### Related
+
+- GAP-03 (Database Passphrase Not Enforced) is compounded by this gap on Desktop.
+- The `testCrypto()` function referenced in `AppCommon.desktop.kt:39` is commented out with a `// LALAL` marker, suggesting crypto testing was planned but never completed.
diff --git a/apps/multiplatform/product/glossary.md b/apps/multiplatform/product/glossary.md
new file mode 100644
index 0000000000..10203d8a2a
--- /dev/null
+++ b/apps/multiplatform/product/glossary.md
@@ -0,0 +1,561 @@
+# Domain Term Glossary -- SimpleX Chat (Android & Desktop, Kotlin Multiplatform)
+
+This glossary is self-contained and covers the Android and Desktop (Kotlin/Compose Multiplatform) codebase only.
+
+---
+
+## Table of Contents
+
+1. [Protocols & Cryptography](#1-protocols--cryptography)
+2. [Core Data Types](#2-core-data-types)
+3. [Commands & Events](#3-commands--events)
+4. [Connection & Identity](#4-connection--identity)
+5. [Messaging Features](#5-messaging-features)
+6. [Calling & Media](#6-calling--media)
+7. [Notifications & Background](#7-notifications--background)
+8. [Application Architecture](#8-application-architecture)
+9. [Configuration & Preferences](#9-configuration--preferences)
+
+---
+
+## 1. Protocols & Cryptography
+
+### SMP (SimpleX Messaging Protocol)
+The core message-relay protocol. Clients send and receive messages through SMP relay servers without exposing sender/receiver identity correlation. The protocol uses unidirectional queues -- each contact pair maintains separate send and receive queues on potentially different servers.
+
+*See:* `common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt` -- `SMPErrorType`, `SMPProxyMode`, `SMPProxyFallback`, `SMPWebPortServers`
+
+### XFTP (SimpleX File Transfer Protocol)
+Protocol for transferring files through relay servers. Files are chunked, encrypted, and uploaded to XFTP relays. Recipients download chunks and reassemble locally. Supports inline transfer for small files.
+
+*See:* `common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt` -- `CC.ApiUploadStandaloneFile`, `CC.ApiDownloadStandaloneFile`, `CC.ApiStandaloneFileInfo`
+
+### E2E Encryption (End-to-End Encryption)
+All messages are encrypted end-to-end. The app never transmits plaintext to relay servers. Encryption keys are negotiated during connection establishment using X3DH-like key agreement and then maintained via the double-ratchet algorithm.
+
+### Double Ratchet
+The core key-management algorithm. After initial key agreement, each message derives a new symmetric key, providing forward secrecy per message. Ratchet state can be re-synchronized via `APISyncContactRatchet` / `APISyncGroupMemberRatchet` commands.
+
+*See:* `SimpleXAPI.kt` -- `CC.APISyncContactRatchet(contactId, force)`, `CC.APISyncGroupMemberRatchet(groupId, groupMemberId, force)`, `CR.ContactRatchetSync`, `CR.GroupMemberRatchetSync`
+
+### PQ (Post-Quantum)
+Post-quantum key exchange support. Connections track PQ state via `Connection.pqSupport`, `Connection.pqEncryption`, `Connection.pqSndEnabled`, and `Connection.pqRcvEnabled` fields. When both peers support PQ, the key exchange incorporates a post-quantum KEM to resist future quantum attacks.
+
+*See:* `ChatModel.kt` -- `Connection.pqSupport`, `Connection.pqEncryption`; `SimpleXAPI.kt` -- `SHARED_PREFS_PQ_EXPERIMENTAL_ENABLED` (legacy, no longer used)
+
+### SMP Proxy / Private Routing
+Messages can be sent through an intermediate SMP proxy relay to hide the sender's IP from the destination relay. Controlled by `SMPProxyMode` (Always, Unknown, Unprotected, Never) and `SMPProxyFallback` (Allow, AllowProtected, Prohibit).
+
+*See:* `SimpleXAPI.kt` -- `AppPreferences.networkSMPProxyMode`, `AppPreferences.networkSMPProxyFallback`
+
+### Transport Session Mode
+Controls how TCP sessions to SMP relays are multiplexed. Options: `User` (one session per user profile), `Session` (single shared session), `Server` (one per server), `Entity` (one per queue/entity -- maximum metadata protection).
+
+*See:* `SimpleXAPI.kt` -- `AppPreferences.networkSessionMode`, `TransportSessionMode`
+
+---
+
+## 2. Core Data Types
+
+### ChatItem
+A single item in a conversation -- a sent or received message, call event, group event, connection event, feature change, or moderation action. Contains direction (`CIDirection`), metadata (`CIMeta`), content (`CIContent`), optional formatted text, mentions, quoted item, reactions, and file attachment.
+
+*See:* `ChatModel.kt:2720` -- `data class ChatItem`
+
+### ChatInfo
+The top-level discriminated union representing a conversation. Variants:
+- `ChatInfo.Direct` -- wraps a `Contact`
+- `ChatInfo.Group` -- wraps a `GroupInfo`
+- `ChatInfo.Local` -- wraps a `NoteFolder` (saved messages / notes to self)
+- `ChatInfo.ContactRequest` -- wraps a `UserContactRequest`
+- `ChatInfo.ContactConnection` -- wraps a `PendingContactConnection`
+- `ChatInfo.InvalidJSON` -- fallback for unrecognized data
+
+*See:* `ChatModel.kt:1391` -- `sealed class ChatInfo`
+
+### CIContent (Chat Item Content)
+The content payload of a `ChatItem`. Over 30 variants including:
+- `SndMsgContent` / `RcvMsgContent` -- regular message with `MsgContent`
+- `SndCall` / `RcvCall` -- call event with status and duration
+- `RcvIntegrityError` -- message integrity violation
+- `RcvDecryptionError` -- decryption failure with error type and count
+- `RcvGroupInvitation` / `SndGroupInvitation` -- group invite
+- `RcvGroupEventContent` / `SndGroupEventContent` -- group lifecycle events
+- `RcvChatFeature` / `SndChatFeature` -- per-chat feature toggle notifications
+- `SndModerated` / `RcvModerated` / `RcvBlocked` -- moderation events
+- `RcvDirectEventContent` -- direct chat lifecycle events
+
+*See:* `ChatModel.kt:3554` -- `sealed class CIContent`
+
+### MsgContent
+The wire-format message body. Variants: `MCText`, `MCLink`, `MCImage`, `MCVideo`, `MCVoice`, `MCFile`, `MCReport`, `MCUnknown`. Each carries text plus optional media/file metadata.
+
+*See:* `ChatModel.kt` -- `sealed class MsgContent`
+
+### User
+The local user profile. Fields: `userId`, `userContactId`, `localDisplayName`, `profile` (LocalProfile), `fullPreferences` (FullChatPreferences), `activeUser`, `activeOrder`, `showNtfs`, `sendRcptsContacts`, `sendRcptsSmallGroups`, `viewPwdHash` (for hidden profiles), `uiThemes`, `remoteHostId` (Long?), `autoAcceptMemberContacts` (Boolean).
+
+*See:* `ChatModel.kt:1208` -- `data class User`
+
+### Contact
+A remote contact. Fields: `contactId`, `localDisplayName`, `profile` (LocalProfile), `activeConn` (Connection?), `viaGroup`, `contactUsed`, `contactStatus`, `chatSettings`, `userPreferences`, `mergedPreferences`, `preparedContact`, `contactRequestId`, `contactGroupMemberId`, `chatTags`, `chatItemTTL`.
+
+*See:* `ChatModel.kt:1711` -- `data class Contact`
+
+### GroupInfo
+Metadata for a group conversation. Fields: `groupId`, `localDisplayName`, `groupProfile` (GroupProfile), `businessChat` (BusinessChatInfo?), `fullGroupPreferences`, `membership` (GroupMember -- the local user's membership), `chatSettings`, `preparedGroup`, `membersRequireAttention`, `chatTags`, `chatItemTTL`.
+
+*See:* `ChatModel.kt:2004` -- `data class GroupInfo`
+
+### GroupMember
+A member of a group. Fields: `groupMemberId`, `groupId`, `memberId`, `memberRole` (GroupMemberRole), `memberCategory` (GroupMemberCategory), `memberStatus` (GroupMemberStatus), `memberSettings` (GroupMemberSettings), `blockedByAdmin`, `invitedBy`, `localDisplayName`, `memberProfile`, `memberContactId`, `memberContactProfileId`, `activeConn` (Connection?), `supportChat` (GroupSupportChat?).
+
+*See:* `ChatModel.kt:2177` -- `data class GroupMember`
+
+### GroupMemberRole
+Enumeration of group roles, ordered for comparison: `Observer` < `Author` < `Member` < `Moderator` < `Admin` < `Owner`. Selectable roles for assignment: Observer, Member, Moderator, Admin, Owner.
+
+*See:* `ChatModel.kt:2369` -- `enum class GroupMemberRole`
+
+### Connection
+An active or pending cryptographic connection to a peer. Fields: `connId`, `agentConnId`, `peerChatVRange` (VersionRange), `connStatus` (ConnStatus), `connLevel`, `viaGroupLink`, `customUserProfileId`, `connectionCode` (SecurityCode?), `pqSupport`, `pqEncryption`, `pqSndEnabled`, `pqRcvEnabled`, `connectionStats`, `authErrCounter`, `quotaErrCounter`.
+
+*See:* `ChatModel.kt:1882` -- `data class Connection`
+
+### Chat
+A composite type holding `chatInfo` (ChatInfo), `chatItems` (list of ChatItem), and `chatStats` (ChatStats -- unread count, min unread item ID, etc.). Represents a full conversation for the chat list.
+
+*See:* `ChatModel.kt` -- `data class Chat`
+
+### PendingContactConnection
+Represents an in-progress connection that has not yet been established. Contains the connection link and state but no contact profile yet.
+
+*See:* `ChatModel.kt` -- referenced in `ChatInfo.ContactConnection`
+
+### CryptoFile
+A file reference that optionally carries `CryptoFileArgs` (key + nonce) for local encryption. `CryptoFile.plain(path)` creates an unencrypted reference.
+
+*See:* `ChatModel.kt` -- `data class CryptoFile`
+
+---
+
+## 3. Commands & Events
+
+The codebase uses short type names for the command/event protocol: `CC` (Chat Command), `CR` (Chat Response -- also carries asynchronous events), `API` (top-level response wrapper), and `ChatError` (error hierarchy). There is no separate "ChatEvent" class; asynchronous events from the core (new messages, connection changes, call signaling) are all `CR` subclasses received via the `recvMsg` loop.
+
+### CC (Chat Command)
+The sealed class representing all commands the app can send to the Haskell core library. Over 140 command variants organized by domain:
+
+**User management:** `ShowActiveUser`, `CreateActiveUser`, `ListUsers`, `ApiSetActiveUser`, `ApiHideUser`, `ApiUnhideUser`, `ApiMuteUser`, `ApiUnmuteUser`, `ApiDeleteUser`
+
+**Chat lifecycle:** `StartChat`, `CheckChatRunning`, `ApiStopChat`, `ApiSetAppFilePaths`, `ApiSetEncryptLocalFiles`
+
+**Database:** `ApiExportArchive`, `ApiImportArchive`, `ApiDeleteStorage`, `ApiStorageEncryption`, `TestStorageEncryption`
+
+**Messaging:** `ApiSendMessages`, `ApiUpdateChatItem`, `ApiDeleteChatItem`, `ApiDeleteMemberChatItem`, `ApiChatItemReaction`, `ApiForwardChatItems`, `ApiPlanForwardChatItems`, `ApiReportMessage`
+
+**Groups:** `ApiNewGroup`, `ApiAddMember`, `ApiJoinGroup`, `ApiAcceptMember`, `ApiMembersRole`, `ApiBlockMembersForAll`, `ApiRemoveMembers`, `ApiLeaveGroup`, `ApiListMembers`, `ApiUpdateGroupProfile`, `APICreateGroupLink`, `APIDeleteGroupLink`, `APIGetGroupLink`, `ApiAddGroupShortLink`
+
+**Connections:** `APIAddContact`, `APIConnect`, `APIConnectPlan`, `APIPrepareContact`, `APIPrepareGroup`, `APIConnectPreparedContact`, `APIConnectPreparedGroup`, `ApiConnectContactViaAddress`
+
+**Contacts:** `ApiDeleteChat`, `ApiClearChat`, `ApiListContacts`, `ApiUpdateProfile`, `ApiSetContactPrefs`, `ApiSetContactAlias`
+
+**Address:** `ApiCreateMyAddress`, `ApiDeleteMyAddress`, `ApiShowMyAddress`, `ApiAddMyAddressShortLink`, `ApiSetProfileAddress`, `ApiSetAddressSettings`
+
+**Calls:** `ApiGetCallInvitations`, `ApiSendCallInvitation`, `ApiRejectCall`, `ApiSendCallOffer`, `ApiSendCallAnswer`, `ApiSendCallExtraInfo`, `ApiEndCall`, `ApiCallStatus`
+
+**Server config:** `ApiGetServerOperators`, `ApiSetServerOperators`, `ApiGetUserServers`, `ApiSetUserServers`, `ApiValidateServers`, `APITestProtoServer`
+
+**Network:** `APISetNetworkConfig`, `APIGetNetworkConfig`, `APISetNetworkInfo`, `ReconnectServer`, `ReconnectAllServers`
+
+**Files:** `ReceiveFile`, `CancelFile`, `ApiUploadStandaloneFile`, `ApiDownloadStandaloneFile`, `ApiStandaloneFileInfo`
+
+**Remote access:** `SetLocalDeviceName`, `ListRemoteHosts`, `StartRemoteHost`, `SwitchRemoteHost`, `StopRemoteHost`, `DeleteRemoteHost`, `StoreRemoteFile`, `GetRemoteFile`, `ConnectRemoteCtrl`, `FindKnownRemoteCtrl`, `ConfirmRemoteCtrl`, `VerifyRemoteCtrlSession`, `ListRemoteCtrls`, `StopRemoteCtrl`, `DeleteRemoteCtrl`
+
+**Read status:** `ApiChatRead`, `ApiChatItemsRead`, `ApiChatUnread`
+
+**Settings:** `APISetChatSettings`, `ApiSetMemberSettings`, `APISetChatItemTTL`, `APIGetChatItemTTL`, `APISetChatTTL`, `ApiSaveSettings`, `ApiGetSettings`
+
+**Ratchet & verification:** `APISwitchContact`, `APISwitchGroupMember`, `APIAbortSwitchContact`, `APIAbortSwitchGroupMember`, `APISyncContactRatchet`, `APISyncGroupMemberRatchet`, `APIGetContactCode`, `APIGetGroupMemberCode`, `APIVerifyContact`, `APIVerifyGroupMember`
+
+Each command variant has a `cmdString` property that serializes it to the text protocol consumed by the Haskell FFI.
+
+*See:* `SimpleXAPI.kt:3529` -- `sealed class CC`
+
+### CR (Chat Response)
+The sealed class representing all responses / events received from the Haskell core. Over 130 response types. Examples:
+
+- `ActiveUser`, `UsersList` -- user management results
+- `ChatStarted`, `ChatRunning`, `ChatStopped` -- lifecycle
+- `ApiChats`, `ApiChat` -- chat list data
+- `NewChatItems`, `ChatItemUpdated`, `ChatItemsDeleted` -- message events
+- `ContactConnected`, `ContactConnecting`, `ContactSndReady` -- connection lifecycle
+- `GroupCreated`, `ReceivedGroupInvitation`, `JoinedGroupMemberConnecting`, `MemberAccepted` -- group events
+- `RcvFileStart`, `RcvFileComplete`, `SndFileComplete` -- file transfer progress
+- `CallInvitation`, `CallOffer`, `CallAnswer`, `CallExtraInfo`, `CallEnded` -- call signaling
+- `ChatError` -- error wrapper
+
+*See:* `SimpleXAPI.kt:6114` -- `sealed class CR`
+
+### API
+The top-level response wrapper. Two variants:
+- `API.Result(remoteHostId, res: CR)` -- successful response
+- `API.Error(remoteHostId, err: ChatError)` -- error response
+
+Properties: `ok` (Boolean -- true if `CR.CmdOk`), `result` (CR?), `rhId` (Long? -- remote host ID).
+
+*See:* `SimpleXAPI.kt:5975` -- `sealed class API`
+
+### ChatError
+The error hierarchy returned from the Haskell core:
+- `ChatErrorChat(errorType: ChatErrorType)` -- application-level errors (NoActiveUser, UserUnknown, DifferentActiveUser, etc.)
+- `ChatErrorAgent(agentError: AgentErrorType)` -- SMP agent errors (BROKER, SMP, PROXY, etc.)
+- `ChatErrorStore(storeError: StoreError)` -- database/store errors
+- `ChatErrorDatabase(databaseError: DatabaseError)` -- database migration/encryption errors
+- `ChatErrorRemoteHost(remoteHostError)` -- remote host control errors
+- `ChatErrorRemoteCtrl(remoteCtrlError)` -- remote controller errors
+- `ChatErrorInvalidJSON(json)` -- parse failure
+
+*See:* `SimpleXAPI.kt:6974` -- `sealed class ChatError`
+
+### sendCmd / recvMsg
+The core FFI bridge. `sendCmd(rhId, cmd)` serializes a `CC` command and sends it to the Haskell backend via `chatSendCmd`. `recvMsg(ctrl)` blocks on `chatRecvMsg` to receive the next `API` response/event. The receiver loop runs in `ChatController.startReceiver()` on `Dispatchers.IO`.
+
+*See:* `SimpleXAPI.kt` -- `ChatController.sendCmd()`, `ChatController.startReceiver()`
+
+---
+
+## 4. Connection & Identity
+
+### SimpleX Address (User Address)
+A long-lived contact address that others can use to send connection requests. Created via `ApiCreateMyAddress`, retrieved via `ApiShowMyAddress`, deleted via `ApiDeleteMyAddress`. Can optionally include a short link (`ApiAddMyAddressShortLink`). Stored as `ChatModel.userAddress` (`UserContactLinkRec`).
+
+### Contact Link / Connection Link
+A one-time or reusable invitation link. The `CreatedConnLink` type wraps the link string. Contact links can be one-time (single use) or long-lived (user address). Created via `APIAddContact` (one-time) or `ApiCreateMyAddress` (reusable).
+
+### Group Link
+A reusable invitation link for joining a group. Created via `APICreateGroupLink(groupId, memberRole)`. The default role for new members joining via the link is configurable. Can also have a short link variant via `ApiAddGroupShortLink`.
+
+### Short Link
+A compact form of a contact or group link. Created via `ApiAddMyAddressShortLink` (for user addresses) or `ApiAddGroupShortLink` (for groups). Short links resolve to the full connection link data including `ContactShortLinkData` or `GroupShortLinkData`.
+
+### Incognito Mode
+When enabled (`AppPreferences.incognito`), the app generates a random profile name for new connections instead of using the user's real profile. Each connection gets a unique random identity. The `customUserProfileId` on a `Connection` tracks which incognito profile is used for that connection.
+
+*See:* `SimpleXAPI.kt` -- `AppPreferences.incognito`; `ChatModel.kt` -- `Connection.customUserProfileId`
+
+### Hidden Profile
+A user profile protected by a password (`viewPwdHash`). Hidden profiles do not appear in the profile list unless unlocked with the password. Created via `ApiHideUser(userId, viewPwd)`, revealed via `ApiUnhideUser(userId, viewPwd)`. When switching away from a hidden profile, its notifications are cancelled.
+
+*See:* `SimpleXAPI.kt` -- `CC.ApiHideUser`, `CC.ApiUnhideUser`; `ChatModel.kt` -- `User.viewPwdHash`
+
+### Connection Verification (Security Code)
+Each connection has an optional `SecurityCode` (`Connection.connectionCode`). Users can verify connections out-of-band by comparing security codes displayed via `APIGetContactCode` / `APIGetGroupMemberCode` and confirming via `APIVerifyContact` / `APIVerifyGroupMember`.
+
+### Connection Plan
+Before connecting via a link, `APIConnectPlan` analyzes the link and returns a `ConnectionPlan` indicating whether the link leads to an existing contact, a new contact, a group join, etc. This prevents duplicate connections.
+
+*See:* `SimpleXAPI.kt` -- `CC.APIConnectPlan`, `CR.CRConnectionPlan`
+
+### Prepared Contact / Prepared Group
+An intermediate state in the connection flow. `APIPrepareContact` / `APIPrepareGroup` creates the local record and displays the contact/group preview before the user confirms the connection. The user can then change the active profile (`APIChangePreparedContactUser` / `APIChangePreparedGroupUser`) and finally confirm via `APIConnectPreparedContact` / `APIConnectPreparedGroup`.
+
+---
+
+## 5. Messaging Features
+
+### Delivery Receipt
+Confirmation that a message was delivered to the recipient's device. Controlled per-user via `sendRcptsContacts` and `sendRcptsSmallGroups` on `User`. The global setting flow is triggered by `ChatModel.setDeliveryReceipts`. Individual overrides per-contact are managed via `ApiSetUserContactReceipts` / `ApiSetUserGroupReceipts`.
+
+*See:* `SimpleXAPI.kt` -- `CC.SetAllContactReceipts`, `CC.ApiSetUserContactReceipts`, `CC.ApiSetUserGroupReceipts`; `AppPreferences.privacyDeliveryReceiptsSet`
+
+### Timed Message (Disappearing Message)
+Messages with a time-to-live after which they are automatically deleted. Configured as a `ChatFeature` / `GroupFeature` with a TTL parameter in seconds. The `customDisappearingMessageTime` preference stores the last custom duration used. Per-chat TTL can be set via `APISetChatTTL`. Global TTL via `APISetChatItemTTL`.
+
+*See:* `SimpleXAPI.kt` -- `CC.APISetChatItemTTL`, `CC.APISetChatTTL`; `AppPreferences.customDisappearingMessageTime`
+
+### Live Message
+A message that updates in real-time as the sender types. Controlled by `CC.ApiSendMessages` with `live=true`. The `ComposeState.liveMessage` tracks the current live message being composed. An alert is shown on first use (`AppPreferences.liveMessageAlertShown`).
+
+### Message Reactions
+Emoji reactions on messages. Added/removed via `ApiChatItemReaction(type, id, scope, itemId, add, reaction)`. Reaction members in groups can be queried via `ApiGetReactionMembers`. Each `ChatItem` carries a `reactions: List`.
+
+### Message Forwarding
+Messages can be forwarded between chats. `ApiPlanForwardChatItems` checks feasibility (e.g., file availability), and `ApiForwardChatItems` performs the forward. A `ForwardConfirmation` may be required if files need downloading first.
+
+### Message Reports
+Users can report messages in groups via `ApiReportMessage(groupId, chatItemId, reportReason, reportText)`. Admins can archive (`ApiArchiveReceivedReports`) or delete (`ApiDeleteReceivedReports`) reports.
+
+### Mentions
+In-message mentions of group members. Stored as `mentions: Map` on `ChatItem` and `mentions: MentionedMembers` on `ComposeState`.
+
+### Link Previews
+Automatic preview generation for URLs in messages. Controlled by `AppPreferences.privacyLinkPreviews`. An alert is shown on first use (`privacyLinkPreviewsShowAlert`).
+
+### Local File Encryption
+Files stored on device can be encrypted. Controlled by `AppPreferences.privacyEncryptLocalFiles` and toggled via `CC.ApiSetEncryptLocalFiles(enable)`.
+
+### Chat Tags
+User-defined tags for organizing conversations. CRUD via `ApiCreateChatTag`, `ApiUpdateChatTag`, `ApiDeleteChatTag`, `ApiReorderChatTags`. Assignment via `ApiSetChatTags`. The model tracks `userTags`, `presetTags` (system-defined categories), `unreadTags`, and the active filter (`activeChatTagFilter`).
+
+---
+
+## 6. Calling & Media
+
+### WebRTC
+The real-time communication framework used for audio and video calls. The app uses WebRTC for peer-to-peer media streams, with SMP used only for call signaling (offer/answer/ICE candidates).
+
+### Call (data class)
+Represents an active call session. Fields: `remoteHostId`, `userProfile`, `contact`, `callUUID`, `callState` (CallState enum), `initialCallType` (Audio/Video), `localMediaSources`, `localCapabilities`, `peerMediaSources`, `sharedKey` (for E2E call encryption), `connectionInfo`, `connectedAt`.
+
+*See:* `common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt:14`
+
+### CallState
+Enum tracking call progression: `WaitCapabilities` -> `InvitationSent` / `InvitationAccepted` -> `OfferSent` / `OfferReceived` -> `Negotiated` -> `Connected` -> `Ended`.
+
+### WCallCommand / WCallResponse
+The command/response protocol between the Kotlin app and the WebRTC JavaScript layer:
+- **Commands:** `Capabilities`, `Permission`, `Start`, `Offer`, `Answer`, `Ice`, `Media`, `Camera`, `Description`, `Layout`, `End`
+- **Responses:** `Capabilities`, `Offer`, `Answer`, `Ice`, `Connection`, `Connected`, `PeerMedia`, `End`, `Ended`, `Ok`, `Error`
+
+*See:* `WebRTC.kt:88` -- `sealed class WCallCommand`; `WebRTC.kt:103` -- `sealed class WCallResponse`
+
+### CallManager
+Manages incoming call invitations and the active call lifecycle. Handles reporting new incoming calls, accepting calls, switching between calls, and ending calls. Interacts with `ChatModel.callInvitations`, `ChatModel.activeCall`, and the platform notification manager.
+
+*See:* `common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt`
+
+### Android: CallActivity
+A dedicated Android `Activity` that displays the call UI. Launched when accepting an incoming call or initiating an outgoing call. Uses an Android `WebView` to host the WebRTC JavaScript.
+
+*See:* `android/src/main/java/chat/simplex/app/views/call/CallActivity.kt`
+
+### Android: CallService
+An Android foreground `Service` that keeps the call alive when the app is in the background. Holds a `WakeLock`, displays an ongoing call notification, and manages the call lifecycle. Uses notification channel `CALL_SERVICE_NOTIFICATION`.
+
+*See:* `android/src/main/java/chat/simplex/app/CallService.kt`
+
+### Desktop: Browser-based WebRTC via NanoWSD
+On Desktop, calls are implemented by opening the system browser to a locally-hosted WebSocket server. A `NanoHTTPD`/`NanoWSD` server runs on `localhost:50395`, serving the WebRTC call page and communicating with the Kotlin app via WebSocket messages. Commands are sent as JSON-serialized `WVAPICall` objects; responses are parsed as `WVAPIMessage` objects.
+
+*See:* `common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt`
+
+### ICE Servers
+STUN/TURN servers used for WebRTC NAT traversal. Configurable via `AppPreferences.webrtcIceServers`. The relay policy (`AppPreferences.webrtcPolicyRelay`) controls whether calls must use TURN relays (for IP privacy) or can attempt direct connections.
+
+### CallMediaType
+Enum: `Video`, `Audio`. Determines the initial media type of the call.
+
+### CallMediaSource
+Enum: `Mic`, `Camera`, `ScreenAudio`, `ScreenVideo`. Used in `WCallCommand.Media` to toggle individual media streams.
+
+---
+
+## 7. Notifications & Background
+
+### Android: SimplexService
+A foreground `Service` that keeps the chat backend running in the background. Uses a `WakeLock` and displays a persistent notification ("SimpleX Chat service" channel). Started with `START_STICKY` for automatic restart. Manages the `chatRecvMsg` loop indirectly by keeping the process alive.
+
+Notification channel: `chat.simplex.app.SIMPLEX_SERVICE_NOTIFICATION` ("SimpleX Chat service")
+
+*See:* `android/src/main/java/chat/simplex/app/SimplexService.kt`
+
+### Android: MessagesFetcherWorker
+A `WorkManager` periodic worker that wakes the app to fetch new messages when the foreground service is not running (i.e., when `NotificationsMode` is `PERIODIC`). Provides a battery-friendly alternative to the always-on service.
+
+*See:* `android/src/main/java/chat/simplex/app/MessagesFetcherWorker.kt`
+
+### Android: NotificationsMode
+Enum controlling background message fetching:
+- `OFF` -- no background activity; messages received only when app is open
+- `PERIODIC` -- uses `MessagesFetcherWorker` for periodic fetches
+- `SERVICE` -- uses `SimplexService` foreground service (default)
+
+*See:* `SimpleXAPI.kt:7739` -- `enum class NotificationsMode`
+
+### Android: Notification Channels
+Android notification channels registered by the app:
+- **Messages:** `chat.simplex.app.MESSAGE_NOTIFICATION` -- high importance, for incoming messages
+- **Calls:** `chat.simplex.app.CALL_NOTIFICATION_2` -- high importance, for incoming call alerts with custom sound
+- **Service:** `chat.simplex.app.SIMPLEX_SERVICE_NOTIFICATION` -- low importance, persistent foreground service indicator
+- **Call Service:** `chat.simplex.app.CALL_SERVICE_NOTIFICATION` -- default importance, ongoing call indicator
+
+*See:* `android/src/main/java/chat/simplex/app/model/NtfManager.android.kt`, `SimplexService.kt`, `CallService.kt`
+
+### Android: NtfManager
+The Android-specific notification manager. Handles creating notification channels, displaying message notifications (with grouping via `MessageGroup`), displaying incoming call notifications (with full-screen intent for lock-screen calls), and managing notification actions (accept/reject call, open chat).
+
+*See:* `android/src/main/java/chat/simplex/app/model/NtfManager.android.kt`
+
+### Desktop: System Notifications
+On Desktop, notifications use the system notification mechanism (typically via the JVM's `SystemTray` or platform-specific notification APIs). The notification manager interface is shared (`ntfManager`) but the implementation is platform-specific.
+
+### NotificationPreviewMode
+Controls what information appears in notifications:
+- `HIDDEN` -- no message content
+- `CONTACT` -- shows sender name only
+- `MESSAGE` -- shows sender name and message preview (default)
+
+*See:* `ChatModel.kt:4823` -- `enum class NotificationPreviewMode`
+
+### Wake Lock Management
+In `ChatController.startReceiver()`, each received message acquires a wake lock (via `getWakeLock(timeout=60000)`) that is released after 30 seconds. This ensures the device stays awake long enough to process incoming messages and display notifications, particularly for incoming calls.
+
+---
+
+## 8. Application Architecture
+
+### ChatController
+The singleton controller that bridges the Kotlin UI layer and the Haskell core library. Responsibilities:
+- Manages the `chatCtrl` (FFI handle to the Haskell runtime)
+- Sends commands via `sendCmd()` and receives events via the `startReceiver()` coroutine loop
+- Processes received messages in `processReceivedMsg()`
+- Holds a reference to `AppPreferences` and `ChatModel`
+- Provides the `messagesChannel` (Kotlin coroutine `Channel`) for consumers to observe events
+- Manages retry logic for transient network errors (`sendCmdWithRetry`)
+
+*See:* `SimpleXAPI.kt:493` -- `object ChatController`
+
+### ChatModel
+The singleton reactive state container for the entire app. Uses Compose `mutableStateOf` and `mutableStateListOf` for reactive UI updates. Key state:
+- `currentUser` -- the active user profile
+- `users` -- list of all user profiles (`UserInfo`)
+- `chatsContext` / `secondaryChatsContext` -- `ChatsContext` holding the chat list
+- `chatId` -- currently open chat
+- `groupMembers` -- members of the currently viewed group
+- `callInvitations` -- pending incoming call invitations
+- `activeCall` -- the currently active call
+- `userAddress` -- the user's SimpleX address
+- `chatItemTTL` -- global message TTL setting
+- `userTags` -- chat tags
+- `terminalItems` -- debug terminal log items
+- Various UI state flags (`showCallView`, `switchingUsersAndHosts`, `clearOverlays`, etc.)
+
+*See:* `ChatModel.kt:86` -- `object ChatModel`
+
+### AppPreferences
+A class wrapping platform-specific key-value storage (`Settings` from `com.russhwolf.settings`). On Android, backed by `SharedPreferences`. On Desktop, backed by Java `Properties` files. Provides type-safe accessors for all user preferences.
+
+*See:* `SimpleXAPI.kt:94` -- `class AppPreferences`
+
+### ComposeState
+Data class holding the state of the message composition area. Fields: `message` (ComposeMessage), `parsedMessage` (formatted text), `liveMessage`, `preview` (ComposePreview), `contextItem` (ComposeContextItem -- reply/edit context), `inProgress`, `progressByTimeout`, `useLinkPreviews`, `mentions`.
+
+*See:* `common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt:98`
+
+### ModalManager
+Manages the modal/sheet presentation stack. Supports multiple placements (default, center, fullscreen, end). Holds an ordered list of `ModalViewHolder` items and exposes `showModal`, `showCustomModal`, `showModalCloseable`, `closeModal`. Uses Compose state (`modalCount`) to trigger recomposition.
+
+*See:* `common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt:92`
+
+### AlertManager
+Singleton for displaying alert dialogs. Provides `showAlertMsg`, `showAlertDialog`, `showAlertDialogButtons`, etc. Works with `AlertManager.shared` for the default instance.
+
+### ChatsContext
+Holds the chat list state for a particular scope (main or secondary). Manages `chats` (State>), provides `updateChats()` to refresh, and supports filtering/keeping specific chats during updates.
+
+### ConnectProgressManager
+Tracks and displays connection progress in the UI. Methods: `startConnectProgress(text, onCancel)`, `stopConnectProgress()`, `cancelConnectProgress()`. Exposes `showConnectProgress` (nullable string indicating active progress text).
+
+*See:* `ChatModel.kt:48` -- `object ConnectProgressManager`
+
+### withBGApi / withLongRunningApi
+Utility functions for launching coroutines on background threads. Used throughout the codebase to perform API calls without blocking the UI thread.
+
+---
+
+## 9. Configuration & Preferences
+
+### AppPreferences (Storage)
+All preferences are accessed through `ChatController.appPrefs`, which is a lazy-initialized `AppPreferences` instance. The underlying storage is:
+- **Android:** `SharedPreferences` with ID `chat.simplex.app.SIMPLEX_APP_PREFS`
+- **Desktop:** Java `Properties` files via `com.russhwolf.settings`
+
+Theme overrides have separate storage (`SHARED_PREFS_THEMES_ID`).
+
+### SharedPreference
+A generic wrapper providing `get()` and `set(value)` for a single preference. All `AppPreferences` fields are `SharedPreference` instances created by factory methods (`mkBoolPreference`, `mkStrPreference`, `mkIntPreference`, `mkLongPreference`, `mkFloatPreference`, `mkEnumPreference`, `mkSafeEnumPreference`, `mkDatePreference`, `mkMapPreference`, `mkTimeoutPreference`).
+
+### Key Preference Categories
+
+**Notifications:**
+- `notificationsMode` -- OFF / PERIODIC / SERVICE
+- `notificationPreviewMode` -- HIDDEN / CONTACT / MESSAGE
+- `canAskToEnableNotifications` -- gate for the notification prompt
+
+**Privacy:**
+- `privacyProtectScreen` -- prevents screenshots (Android FLAG_SECURE)
+- `privacyAcceptImages` -- auto-accept inline images
+- `privacyLinkPreviews` -- generate URL previews
+- `privacySanitizeLinks` -- strip tracking parameters from URLs
+- `privacyShowChatPreviews` -- show message preview in chat list
+- `privacySaveLastDraft` -- persist draft messages
+- `privacyEncryptLocalFiles` -- encrypt files at rest
+- `privacyAskToApproveRelays` -- prompt before using relays suggested by contacts
+- `privacyMediaBlurRadius` -- blur radius for media in notifications/previews
+
+**Security:**
+- `performLA` -- require local authentication (biometric/PIN)
+- `laMode` -- local authentication mode
+- `laLockDelay` -- seconds before re-locking
+- `storeDBPassphrase` -- whether to persist the DB passphrase
+- `initialRandomDBPassphrase` -- indicates the DB uses a random (non-user-chosen) passphrase
+- `selfDestruct` -- enable self-destruct profile
+- `selfDestructDisplayName` -- display name for the self-destruct profile
+
+**Network:**
+- `networkUseSocksProxy` -- route traffic through SOCKS proxy
+- `networkProxy` -- SOCKS proxy host/port configuration
+- `networkSessionMode` -- transport session multiplexing mode
+- `networkSMPProxyMode` -- SMP proxy / private routing mode
+- `networkSMPProxyFallback` -- fallback behavior when proxy fails
+- `networkHostMode` -- onion/public host preference
+- `networkRequiredHostMode` -- enforce host mode strictly
+- Various TCP timeout settings (background, interactive, per-KB)
+- Keep-alive settings (idle, interval, count)
+
+**Calls:**
+- `webrtcPolicyRelay` -- force TURN relay usage
+- `callOnLockScreen` -- DISABLE / SHOW / ACCEPT calls on lock screen
+- `webrtcIceServers` -- custom ICE server configuration
+- `experimentalCalls` -- enable experimental call features
+
+**Appearance:**
+- `currentTheme` -- active theme name
+- `systemDarkTheme` -- theme for system dark mode
+- `themeOverrides` -- per-theme customizations
+- `profileImageCornerRadius` -- avatar rounding
+- `chatItemRoundness` -- message bubble rounding
+- `chatItemTail` -- show/hide message bubble tail
+- `fontScale` -- text size scaling
+- `densityScale` -- UI density scaling
+- `inAppBarsAlpha` -- toolbar transparency
+- `appearanceBarsBlurRadius` -- toolbar blur effect
+
+**UI:**
+- `oneHandUI` -- one-handed UI mode (bottom-aligned navigation)
+- `chatBottomBar` -- show bottom bar in chat view
+- `simplexLinkMode` -- how SimpleX links are displayed (DESCRIPTION / FULL / BROWSER)
+- `showUnreadAndFavorites` -- filter chat list to unread/favorites
+- `developerTools` -- enable developer tools (terminal, etc.)
+
+**Database:**
+- `encryptedDBPassphrase` -- encrypted form of the DB passphrase
+- `initializationVectorDBPassphrase` -- IV for DB passphrase encryption
+- `encryptionStartedAt` -- timestamp of encryption operation start (for crash recovery)
+- `confirmDBUpgrades` -- prompt before database migrations
+- `newDatabaseInitialized` -- flag for incomplete initialization recovery
+
+**Remote Access:**
+- `deviceNameForRemoteAccess` -- device display name for remote control
+- `confirmRemoteSessions` -- require confirmation for remote sessions
+- `connectRemoteViaMulticast` -- use multicast discovery
+- `connectRemoteViaMulticastAuto` -- auto-connect via multicast
+- `desktopWindowState` -- persisted window position/size (Desktop only)
+
+**Migration:**
+- `migrationToStage` / `migrationFromStage` -- track migration progress
+- `onboardingStage` -- current onboarding step
+- `lastMigratedVersionCode` -- last app version that ran migrations
+
+*See:* `SimpleXAPI.kt:94-489` -- `class AppPreferences` with all `SHARED_PREFS_*` constants
diff --git a/apps/multiplatform/product/rules.md b/apps/multiplatform/product/rules.md
new file mode 100644
index 0000000000..90a2dadada
--- /dev/null
+++ b/apps/multiplatform/product/rules.md
@@ -0,0 +1,253 @@
+# Business Rules -- SimpleX Chat (Android & Desktop, Kotlin Multiplatform)
+
+This document specifies invariants enforced by the Android and Desktop (Kotlin/Compose Multiplatform) clients.
+
+---
+
+## Table of Contents
+
+1. [Security (RULE-01 through RULE-05)](#1-security)
+2. [Message Integrity (RULE-06 through RULE-09)](#2-message-integrity)
+3. [Group Integrity (RULE-10 through RULE-13)](#3-group-integrity)
+4. [File Transfer (RULE-14 through RULE-15)](#4-file-transfer)
+5. [Notification Delivery (RULE-16 through RULE-17)](#5-notification-delivery)
+6. [Call Integrity (RULE-18)](#6-call-integrity)
+
+---
+
+## 1. Security
+
+### RULE-01: End-to-End Encryption is Mandatory
+
+**Invariant:** Every message, file chunk, and call signaling payload MUST be encrypted end-to-end before transmission. The app MUST NOT transmit plaintext content to any relay server.
+
+**Enforcement:** The Haskell core library handles all encryption. The Kotlin layer never constructs raw SMP messages. All communication flows through `ChatController.sendCmd()` which delegates to the FFI, ensuring the encryption layer cannot be bypassed.
+
+**Location:** `common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt` -- `ChatController.sendCmd()`, `chatSendCmd()` FFI call
+
+---
+
+### RULE-02: Database Encryption at Rest
+
+**Invariant:** The local SQLite database MUST be encrypted. A passphrase (either user-chosen or randomly generated) MUST be set before the database is operational.
+
+**Enforcement:** On first launch, a random passphrase is generated and stored encrypted via the platform keystore (`CryptorInterface.encryptText`). The `initialRandomDBPassphrase` preference tracks whether the user has set a custom passphrase. Database encryption state is tracked in `ChatModel.chatDbEncrypted`. Encryption/re-encryption is performed via `CC.ApiStorageEncryption(config: DBEncryptionConfig)`.
+
+**Caveat:** The user is not forced to set a custom passphrase -- the random passphrase is stored in app-accessible encrypted preferences. See GAP: "Database passphrase not enforced."
+
+**Location:**
+- `common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt`
+- `common/src/commonMain/kotlin/chat/simplex/common/platform/Cryptor.kt` -- `CryptorInterface`
+- Android: `common/src/androidMain/kotlin/chat/simplex/common/platform/Cryptor.android.kt` -- Android Keystore
+- Desktop: `common/src/desktopMain/kotlin/chat/simplex/common/platform/Cryptor.desktop.kt` -- **placeholder, not implemented**
+
+---
+
+### RULE-03: Local Authentication Gating
+
+**Invariant:** When local authentication is enabled (`AppPreferences.performLA == true`), the app MUST require biometric/PIN authentication before displaying any chat content. The lock engages after `laLockDelay` seconds of inactivity.
+
+**Enforcement:** `AppLock.setPerformLA` controls the lock state. The lock delay is configurable via `AppPreferences.laLockDelay` (default 30 seconds). Authentication mode is set via `AppPreferences.laMode` (system biometric or passcode).
+
+**Location:**
+- `common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt`
+- `SimpleXAPI.kt` -- `AppPreferences.performLA`, `AppPreferences.laMode`, `AppPreferences.laLockDelay`
+
+---
+
+### RULE-04: Self-Destruct Profile
+
+**Invariant:** When self-destruct is enabled (`AppPreferences.selfDestruct == true`), entering the self-destruct passphrase instead of the real passphrase MUST wipe the database and present a clean profile with `selfDestructDisplayName`.
+
+**Enforcement:** The self-destruct passphrase is stored separately (`encryptedSelfDestructPassphrase` / `initializationVectorSelfDestructPassphrase`). On Android, `SimplexService` checks for self-destruct on initialization. The comparison happens during the local authentication flow.
+
+**Location:**
+- `SimpleXAPI.kt` -- `AppPreferences.selfDestruct`, `AppPreferences.selfDestructDisplayName`
+- `android/src/main/java/chat/simplex/app/SimplexService.kt` -- initialization check
+
+---
+
+### RULE-05: Screen Protection
+
+**Invariant:** When `AppPreferences.privacyProtectScreen == true` (default), the app MUST prevent screenshots and screen recording. On Android this uses `FLAG_SECURE`; on Desktop this is advisory only.
+
+**Enforcement:** The preference defaults to `true`. The Android activity applies `FLAG_SECURE` to its window based on this preference. The Desktop app cannot enforce this at the OS level.
+
+**Location:** `SimpleXAPI.kt` -- `AppPreferences.privacyProtectScreen`
+
+---
+
+## 2. Message Integrity
+
+### RULE-06: Message Ordering Verification
+
+**Invariant:** The app MUST detect and surface message integrity violations (gaps, duplicates, out-of-order delivery) to the user.
+
+**Enforcement:** The Haskell core tracks message sequence numbers per connection. When a gap or integrity error is detected, a `CIContent.RcvIntegrityError(msgError: MsgErrorType)` chat item is inserted into the conversation. The UI renders these as system messages indicating the integrity issue.
+
+**Location:** `ChatModel.kt:3565` -- `CIContent.RcvIntegrityError`
+
+---
+
+### RULE-07: Decryption Error Surfacing
+
+**Invariant:** When a message cannot be decrypted, the app MUST display a `RcvDecryptionError` item showing the error type and count of affected messages. The app MUST NOT silently drop undecryptable messages.
+
+**Enforcement:** The Haskell core emits `CIContent.RcvDecryptionError(msgDecryptError, msgCount)` which the UI renders with an explanation and count. Ratchet re-synchronization can be triggered via `APISyncContactRatchet` / `APISyncGroupMemberRatchet`.
+
+**Location:** `ChatModel.kt:3566` -- `CIContent.RcvDecryptionError`
+
+---
+
+### RULE-08: Delivery Receipt Consistency
+
+**Invariant:** Delivery receipt settings MUST be consistent: when a user enables/disables receipts globally, the change MUST propagate to all contacts/groups (optionally clearing per-chat overrides via `clearOverrides`).
+
+**Enforcement:** Global receipt toggle triggers `CC.SetAllContactReceipts(enable)`. Per-type settings use `CC.ApiSetUserContactReceipts` / `CC.ApiSetUserGroupReceipts` with `UserMsgReceiptSettings(enable, clearOverrides)`. The `privacyDeliveryReceiptsSet` preference gates the initial setup prompt shown during onboarding.
+
+**Location:**
+- `SimpleXAPI.kt` -- `CC.SetAllContactReceipts`, `CC.ApiSetUserContactReceipts`, `CC.ApiSetUserGroupReceipts`
+- `SimpleXAPI.kt` -- `ChatController.startChat()` -- triggers `setDeliveryReceipts` prompt
+
+---
+
+### RULE-09: Chat Item TTL Enforcement
+
+**Invariant:** When a chat item TTL (time-to-live) is set globally or per-chat, expired messages MUST be deleted by the core. The app MUST NOT display expired items.
+
+**Enforcement:** Global TTL set via `CC.APISetChatItemTTL(userId, seconds)`. Per-chat TTL set via `CC.APISetChatTTL(userId, chatType, id, seconds)`. The Haskell core performs periodic cleanup. The current global TTL is stored in `ChatModel.chatItemTTL`.
+
+**Location:** `SimpleXAPI.kt` -- `CC.APISetChatItemTTL`, `CC.APISetChatTTL`
+
+---
+
+## 3. Group Integrity
+
+### RULE-10: Role-Based Access Control
+
+**Invariant:** Group operations MUST respect the member's role. Only members with sufficient role level can perform privileged operations:
+- **Owner:** can delete group, change any member's role, transfer ownership
+- **Admin:** can add/remove members, change roles (up to Admin), create/delete group links
+- **Moderator:** can delete other members' messages, block members
+- **Member / Author / Observer:** cannot perform administrative actions
+
+**Enforcement:** The Haskell core validates role permissions server-side. The Kotlin UI layer uses `GroupMemberRole` comparisons (the enum is ordered: Observer < Author < Member < Moderator < Admin < Owner) to show/hide action buttons.
+
+**Location:** `ChatModel.kt:2369` -- `enum class GroupMemberRole`; various group management views
+
+---
+
+### RULE-11: Group Member Removal Atomicity
+
+**Invariant:** When removing members from a group, the removal command MUST specify all member IDs atomically. Partial removal MUST NOT leave the group in an inconsistent state.
+
+**Enforcement:** `CC.ApiRemoveMembers(groupId, memberIds: List, withMessages: Boolean)` sends all member IDs in a single command. The `withMessages` flag controls whether the removed members' messages are also deleted.
+
+**Location:** `SimpleXAPI.kt` -- `CC.ApiRemoveMembers`
+
+---
+
+### RULE-12: Group Link Role Default
+
+**Invariant:** When creating a group link, the default member role for joiners MUST be explicitly specified. The role can be updated after creation without regenerating the link.
+
+**Enforcement:** `CC.APICreateGroupLink(groupId, memberRole)` requires a role. `CC.APIGroupLinkMemberRole(groupId, memberRole)` updates it. The link itself remains stable.
+
+**Location:** `SimpleXAPI.kt` -- `CC.APICreateGroupLink`, `CC.APIGroupLinkMemberRole`
+
+---
+
+### RULE-13: Member Blocking Scope
+
+**Invariant:** Blocking a member (`ApiBlockMembersForAll`) MUST apply the block for all group members (not just the requester). The `blocked` flag is visible to all members. Only roles >= Moderator can block.
+
+**Enforcement:** `CC.ApiBlockMembersForAll(groupId, memberIds, blocked)` sends the block/unblock to the core, which propagates it to all group members.
+
+**Location:** `SimpleXAPI.kt` -- `CC.ApiBlockMembersForAll`; `ChatModel.kt` -- `GroupMember.blockedByAdmin`
+
+---
+
+## 4. File Transfer
+
+### RULE-14: File Encryption in Transit and at Rest
+
+**Invariant:** Files sent via XFTP MUST be encrypted before upload. Files received MUST be decrypted only after download. When `privacyEncryptLocalFiles` is enabled (default `true`), files stored locally MUST be encrypted with per-file keys (`CryptoFile.cryptoArgs`).
+
+**Enforcement:** The Haskell core handles XFTP encryption. Local file encryption is toggled via `CC.ApiSetEncryptLocalFiles(enable)`. The `CryptoFile` type carries optional `CryptoFileArgs` (key + nonce) for local decryption. Files are decrypted on-demand for display via `decryptCryptoFile()`.
+
+**Location:**
+- `SimpleXAPI.kt` -- `CC.ApiSetEncryptLocalFiles`, `AppPreferences.privacyEncryptLocalFiles`
+- `ChatModel.kt` -- `CryptoFile`, `CryptoFileArgs`
+- `RecAndPlay.desktop.kt` -- `decryptCryptoFile()` usage in audio playback
+
+---
+
+### RULE-15: Relay Approval for File Transfer
+
+**Invariant:** When `privacyAskToApproveRelays` is enabled (default `true`), the app MUST prompt the user before using XFTP relay servers suggested by contacts (as opposed to the user's own configured servers). The `userApprovedRelays` flag on `CC.ReceiveFile` records the user's consent.
+
+**Enforcement:** `CC.ReceiveFile(fileId, userApprovedRelays, encrypt, inline)` passes the approval flag. The UI prompts the user when the file is from an unapproved relay.
+
+**Location:** `SimpleXAPI.kt` -- `CC.ReceiveFile`, `AppPreferences.privacyAskToApproveRelays`
+
+---
+
+## 5. Notification Delivery
+
+### RULE-16: Background Message Delivery (Android)
+
+**Invariant:** On Android, when `NotificationsMode.SERVICE` is selected (default), the app MUST maintain a foreground service (`SimplexService`) to ensure continuous message delivery. The service MUST survive app backgrounding and device sleep. When `NotificationsMode.PERIODIC` is selected, `MessagesFetcherWorker` MUST periodically wake and fetch messages. When `NotificationsMode.OFF`, no background delivery occurs.
+
+**Enforcement:**
+- `SimplexService` runs as a foreground service with `START_STICKY` and a `WakeLock`. It displays a persistent notification on the `SIMPLEX_SERVICE_NOTIFICATION` channel.
+- `MessagesFetcherWorker` is a `PeriodicWorkRequest` scheduled via `WorkManager`.
+- The mode is stored in `AppPreferences.notificationsMode` and checked at app startup.
+
+**Location:**
+- `android/src/main/java/chat/simplex/app/SimplexService.kt`
+- `android/src/main/java/chat/simplex/app/MessagesFetcherWorker.kt`
+- `SimpleXAPI.kt:7739` -- `enum class NotificationsMode`
+
+---
+
+### RULE-17: Notification Preview Privacy
+
+**Invariant:** Notification content MUST respect `notificationPreviewMode`:
+- `HIDDEN` -- notification shows no sender or message content
+- `CONTACT` -- notification shows sender name only
+- `MESSAGE` -- notification shows sender name and message preview
+
+**Enforcement:** `NtfManager` (Android) reads the preview mode from `AppPreferences.notificationPreviewMode` and constructs notifications accordingly. The `CallService` also respects this mode for call notifications (showing or hiding caller identity).
+
+**Location:**
+- `android/src/main/java/chat/simplex/app/model/NtfManager.android.kt` -- `displayNotification()`, `notifyCallInvitation()`
+- `android/src/main/java/chat/simplex/app/CallService.kt` -- `updateNotification()`
+- `SimpleXAPI.kt` -- `AppPreferences.notificationPreviewMode`
+
+---
+
+## 6. Call Integrity
+
+### RULE-18: Call Lifecycle Management
+
+**Invariant:** An active call MUST be properly managed across the full lifecycle:
+1. **Incoming calls** MUST be reported via `CallManager.reportNewIncomingCall()` which triggers a notification (and on Android, a full-screen intent for lock-screen display).
+2. **Only one call** can be active at a time. Accepting a new call MUST end any existing call first (`CallManager.acceptIncomingCall` checks `activeCall` and calls `endCall` if needed, guarded by `switchingCall` flag).
+3. **Call state** MUST progress through defined states: `WaitCapabilities` -> `InvitationSent`/`InvitationAccepted` -> `OfferSent`/`OfferReceived` -> `Negotiated` -> `Connected` -> `Ended`.
+4. **Call end** MUST clean up all resources: send `WCallCommand.End`, call `apiEndCall`, clear `activeCall`, cancel call notifications, and release platform resources.
+
+**Android enforcement:**
+- `CallService` (foreground service) keeps the call alive in background with a `WakeLock` and ongoing notification on `CALL_SERVICE_NOTIFICATION` channel.
+- `CallActivity` hosts the WebRTC WebView.
+- Lock-screen behavior controlled by `AppPreferences.callOnLockScreen` (DISABLE / SHOW / ACCEPT).
+
+**Desktop enforcement:**
+- Calls run in the system browser via the NanoWSD WebSocket server on `localhost:50395`.
+- The `WebRTCController` composable manages the WebSocket lifecycle.
+- On dispose, `WCallCommand.End` is sent and the server is stopped.
+
+**Location:**
+- `common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt`
+- `common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt`
+- Android: `android/src/main/java/chat/simplex/app/CallService.kt`, `android/src/main/java/chat/simplex/app/views/call/CallActivity.kt`
+- Desktop: `common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt`
diff --git a/apps/multiplatform/product/views/call.md b/apps/multiplatform/product/views/call.md
new file mode 100644
index 0000000000..51d323874c
--- /dev/null
+++ b/apps/multiplatform/product/views/call.md
@@ -0,0 +1,115 @@
+# Audio / Video Call
+
+> **Related spec:** [spec/services/calls.md](../../spec/services/calls.md)
+
+## Purpose
+
+Make and receive end-to-end encrypted audio and video calls over WebRTC. The implementation differs significantly between Android (WebView-based with `CallActivity` and PiP support) and Desktop (browser-based WebRTC via NanoHTTPD server on localhost).
+
+## Route / Navigation
+
+- **Entry point (outgoing)**: Tap audio or video call button in `ChatInfoView` action buttons or `ChatView` toolbar
+- **Entry point (incoming)**: `IncomingCallAlertView` banner appears at top of screen
+- **Presented by**: `ActiveCallView()` (expect/actual composable) is shown when `chatModel.showCallView == true`
+- **Dismiss**: Call ends when user taps end button or remote party disconnects; `callManager.endCall()` handles cleanup
+- **Android PiP**: Call view supports picture-in-picture mode via `CallActivity`
+
+## Platform Differences
+
+| Aspect | Android | Desktop |
+|---|---|---|
+| WebRTC host | `WebView` with `WebViewAssetLoader` serving local assets | NanoHTTPD server on `localhost:50395` opened in system browser |
+| Call activity | `CallActivity` (separate Android Activity) with lifecycle management | Inline composable with `WebRTCController` |
+| PiP support | Native Android PiP via `CallActivity` | Not supported |
+| Audio management | `CallAudioDeviceManager` with Android `AudioManager`, proximity wake lock | System browser audio routing |
+| WebSocket | N/A | `NanoWSD` WebSocket server for bidirectional WebRTC signaling |
+
+## Page Sections
+
+### Incoming Call Banner (`IncomingCallAlertView`)
+
+Displayed as an overlay banner when `chatModel.activeCallInvitation` is set:
+
+| Element | Description |
+|---|---|
+| User profile image | Shown when multiple profiles exist (32dp `ProfileImage`) |
+| Call type icon | `ic_videocam_filled` (green) for video, `ic_call_filled` (green) for audio |
+| Call type text | `invitation.callTypeText` with caller info |
+| Caller profile | `ProfilePreview` showing caller name and avatar (64dp) |
+| Reject button | Red `ic_call_end_filled` icon -- ends the invitation via `callManager.endCall(invitation)` |
+| Ignore button | Blue `ic_close` icon -- dismisses banner, cancels notification |
+| Accept button | Green `ic_check_filled` icon -- accepts via `callManager.acceptIncomingCall(invitation)` |
+
+Sound: `SoundPlayer.start()` plays ringtone while banner is visible (unless call view is already showing).
+
+### Active Call View
+
+#### Android (`CallView.android.kt`)
+
+| Element | Description |
+|---|---|
+| WebView | `AndroidView` wrapping a `WebView` that loads `call.html` via `WebViewAssetLoader`; handles WebRTC JS bridge |
+| `ActiveCallState` | Manages proximity lock (`PROXIMITY_SCREEN_OFF_WAKE_LOCK`), audio device manager, call sounds |
+| Call controls overlay | Mic toggle, speaker toggle, camera switch, video toggle, end call button |
+| Audio device selection | `CallAudioDeviceManager` with device enumeration (earpiece, speaker, Bluetooth, wired headset) |
+| Permissions | Runtime permission checks for `CAMERA` and `RECORD_AUDIO` via Accompanist permissions library |
+
+#### Desktop (`CallView.desktop.kt`)
+
+| Element | Description |
+|---|---|
+| NanoHTTPD server | HTTP server on `localhost:50395` serving `call.html` and assets |
+| NanoWSD WebSocket | WebSocket endpoint for bidirectional signaling between Kotlin and browser JS |
+| `WebRTCController` | Processes `WCallCommand`/`WCallResponse` messages via `chatModel.callCommand` channel |
+| Browser launch | `LocalUriHandler.openUri("http://localhost:50395/call.html")` opens system browser |
+| Connection list | `connections: ArrayList` tracks active WebSocket connections |
+
+### WebRTC Signaling Flow
+
+| Step | Command/Response | Description |
+|---|---|---|
+| 1. Capabilities | `WCallResponse.Capabilities` | Local capabilities reported; `apiSendCallInvitation()` called |
+| 2. Offer | `WCallResponse.Offer` | SDP offer + ICE candidates sent via `apiSendCallOffer()` |
+| 3. Answer | `WCallResponse.Answer` | SDP answer + ICE candidates sent via `apiSendCallAnswer()` |
+| 4. ICE | `WCallResponse.Ice` | Additional ICE candidates exchanged via `apiSendCallExtraInfo()` |
+| 5. Connection | `WCallResponse.Connection` | WebRTC connection state changes; `CallState.Connected` set on success |
+| 6. Connected | `WCallResponse.Connected` | Connection info (relay/direct) stored in `call.connectionInfo` |
+| 7. PeerMedia | `WCallResponse.PeerMedia` | Remote party media source changes (mic, camera, screen) |
+| 8. Media control | `WCallCommand.Media` | Toggle local media sources (mic, camera, screen audio/video) |
+| 9. Camera switch | `WCallCommand.Camera` | Switch between front/back camera |
+| 10. End | `WCallResponse.End` / `WCallResponse.Ended` | Call termination; cleanup and UI dismissal |
+
+### Call States (`CallState`)
+
+| State | Description |
+|---|---|
+| `WaitCapabilities` | Waiting for WebRTC capabilities |
+| `InvitationSent` | Call invitation sent to remote party |
+| `InvitationAccepted` | Callee accepted, starting WebRTC |
+| `OfferSent` | SDP offer sent |
+| `OfferReceived` | Callee received SDP offer |
+| `AnswerReceived` | Caller received SDP answer |
+| `Negotiated` | ICE negotiation complete |
+| `Connected` | WebRTC media flowing; `connectedAt` timestamp set |
+| `Ended` | Call terminated |
+
+### Call Sounds
+
+| Sound | Trigger |
+|---|---|
+| Connecting sound | `CallSoundsPlayer.startConnectingCallSound()` after invitation sent |
+| In-call sound | `CallSoundsPlayer.startInCallSound()` when delivery receipt received |
+| Ringtone | `SoundPlayer.start()` for incoming calls |
+| End vibration | `CallSoundsPlayer.vibrate()` on call end (if was connected) |
+
+## Source Files
+
+| File | Path |
+|---|---|
+| `CallView.kt` | `views/call/CallView.kt` (common expect declarations) |
+| `CallView.android.kt` | `androidMain/.../views/call/CallView.android.kt` |
+| `CallView.desktop.kt` | `desktopMain/.../views/call/CallView.desktop.kt` |
+| `IncomingCallAlertView.kt` | `views/call/IncomingCallAlertView.kt` |
+| `CallManager.kt` | `views/call/CallManager.kt` |
+| `WebRTC.kt` | `views/call/WebRTC.kt` |
+| `CallAudioDeviceManager.kt` | `androidMain/.../views/call/CallAudioDeviceManager.kt` |
diff --git a/apps/multiplatform/product/views/chat-list.md b/apps/multiplatform/product/views/chat-list.md
new file mode 100644
index 0000000000..daa7907c5d
--- /dev/null
+++ b/apps/multiplatform/product/views/chat-list.md
@@ -0,0 +1,136 @@
+# Chat List (Home Screen)
+
+> **Related spec:** [spec/client/chat-list.md](../../spec/client/chat-list.md)
+
+## Purpose
+
+Main screen of the SimpleX Chat Android and Desktop apps. Displays all conversations sorted by last activity, serves as the navigation root, and provides access to user profiles, settings, and new chat creation.
+
+## Route / Navigation
+
+- **Entry point**: App launch (root view), or back-navigation from any chat
+- **Presented by**: `ChatListView` composable as the default view when `chatModel.chatId == null`
+- **Navigation**: `ChatListNavLinkView` handles click routing to `ChatView` for each chat type
+- **UserPicker**: Triggered by tapping the user avatar in the toolbar; presents `UserPicker` as a custom sheet (Android: bottom sheet overlay; Desktop: sidebar panel)
+
+## Platform Layout
+
+| Platform | Layout |
+|---|---|
+| Android | Single-column list; toolbar at top or bottom (one-hand UI); FAB for new chat |
+| Desktop | 3-column layout: chat list (left), chat view (center), info/detail panel (right via `ModalManager.end`) |
+
+## Page Sections
+
+### Toolbar (`ChatListToolbar`)
+
+| Element | Location | Behavior |
+|---|---|---|
+| User avatar button | Leading | Opens `UserPicker` sheet (profile switcher, address, settings, preferences, connect to desktop/mobile) |
+| "Your chats" title | Center | Tappable to scroll list to top |
+| Connection status indicator (`SubscriptionStatusIndicator`) | Adjacent to title | Shows SMP server subscription status; taps open `ServersSummaryView` |
+| New chat button (pencil icon) | Trailing (one-hand UI) or FAB (standard) | Opens `NewChatSheet` modal via `showNewChatSheet()` |
+| Active call indicator | Trailing (Desktop, one-hand UI) | `ActiveCallInteractiveArea` shown when a call is active |
+| Updating progress | Trailing | Shows progress circle/indicator during database updates |
+| Stopped indicator | Trailing | Red warning icon when chat engine is stopped |
+
+The toolbar supports two layout modes controlled by `appPrefs.oneHandUI`:
+- **Standard (top)**: `DefaultAppBar` at top with `NavigationButtonMenu` leading, title center, buttons trailing. FAB at bottom-right for new chat.
+- **One-hand UI (bottom)**: Toolbar at bottom of screen with `Column(Modifier.align(Alignment.BottomCenter))`; list rendered with `reverseLayout = true`; no FAB (new chat button is inline in toolbar).
+
+### Search Bar (`ChatListSearchBar`)
+
+| Element | Description |
+|---|---|
+| Search icon | Magnifying glass icon at leading edge |
+| Text field | `SearchTextField` with placeholder "Search or paste SimpleX link" |
+| Filter button | `ToggleFilterEnabledButton` (filter icon) toggles unread-only filter; shown when search text is empty |
+| Clear button | Appears when text is entered; `BackHandler` clears search on back |
+
+Behavior:
+- Filters chat list in real-time by contact/group name via `filteredChats()`
+- Detects pasted SimpleX links (`strHasSingleSimplexLink`) and triggers `planAndConnect()` connection dialogue
+- In one-hand UI mode, search bar appears below tag filters with IME spacer; in standard mode, above tag filters
+
+### Chat Filter Tags (`TagsView`)
+
+Managed by `chatModel.userTags`, `chatModel.presetTags`, and `chatModel.activeChatTagFilter`:
+
+| Filter | `PresetTagKind` | Icon | Description |
+|---|---|---|---|
+| Group Reports | `GROUP_REPORTS` | Flag | Chats with moderation reports (non-collapsible) |
+| Favorites | `FAVORITES` | Star | User-favorited chats |
+| Contacts | `CONTACTS` | Person | Direct contacts and contact requests |
+| Groups | `GROUPS` | Group | Group conversations (non-business) |
+| Business | `BUSINESS` | Work | Business chat conversations |
+| Notes | `NOTES` | Folder | Notes to self |
+| Custom tags | `UserTag(ChatTag)` | Label/emoji | User-created tags with custom emoji and name |
+| Unread | `ActiveFilter.Unread` | Filter list icon | Chats with unread messages (toggle via filter button) |
+
+Display logic:
+- When collapsible preset tags exceed 3 total (with user tags), they collapse into a `CollapsedTagsFilterView` dropdown menu
+- Non-collapsible tags (`GROUP_REPORTS`) always show expanded
+- User tags show with emoji or label icon; long-press opens `TagsDropdownMenu` (edit, delete, change order)
+- "+" button at end opens `TagListEditor` for creating new tags
+
+### Chat Preview Rows (`ChatPreviewView`)
+
+Each row rendered by `ChatPreviewView` inside `ChatListNavLinkView`:
+
+| Element | Description |
+|---|---|
+| Avatar | `ProfileImage` with overlay icons (inactive contact, left/removed group member) |
+| Chat name | Display name with verified icon for verified contacts; colored for pending/connecting states |
+| Last message preview | Truncated text of most recent message; draft indicator with edit icon; attachment icons |
+| Timestamp | Relative time of last activity |
+| Unread badge | Numeric count badge; distinct styling for mentions |
+| Muted indicator | Bell-off icon when notifications are muted |
+| Favorite indicator | Star icon for favorited chats |
+| Incognito indicator | Shows when connected via incognito profile |
+| Connection status | Shows connecting/pending state for incomplete connections |
+
+Chat types handled by `ChatListNavLinkView`:
+- `ChatInfo.Direct` -- direct contact chat
+- `ChatInfo.Group` -- group chat (with in-progress indicator for joining)
+- `ChatInfo.Local` -- note-to-self folder
+- `ChatInfo.ContactRequest` -- incoming contact request (tap shows accept/reject alert)
+- `ChatInfo.ContactConnection` -- pending connection (tap opens `ContactConnectionView`)
+
+### Context Menu (Long Press / Right Click)
+
+Each chat type provides specific dropdown menu items:
+
+| Chat Type | Menu Items |
+|---|---|
+| Direct contact | Mark read/unread, toggle favorite, toggle notify, tag list, clear chat, delete contact |
+| Group | Mark read/unread, toggle favorite, toggle notify, tag list, clear chat, archive all reports (moderator, when reports exist), leave group, delete group |
+| Note folder | Mark read/unread, clear notes |
+| Contact request | Accept, reject |
+| Contact connection | Set name/alias, delete |
+
+### Floating Elements
+
+| Element | Condition | Description |
+|---|---|---|
+| One-hand UI card (`ToggleChatListCard`) | `oneHandUICardShown == false` | Dismissible card introducing bottom toolbar mode with toggle switch |
+| Address creation card (`AddressCreationCard`) | `addressCreationCardShown == false` | Prompts user to create a SimpleX address; tappable card opens `UserAddressLearnMore` |
+| FAB (new chat button) | Standard mode, search empty, chat running | `FloatingActionButton` at bottom-right, pencil icon, opens `NewChatSheet` |
+
+### Empty States
+
+| State | Display |
+|---|---|
+| Loading | "Loading chats..." centered text |
+| No chats | "You have no chats" centered text |
+| No filtered chats | "No chats in list [tag name]" or "No unread chats" with clickable filter reset |
+| No search results | "No chats found" centered text |
+
+## Source Files
+
+| File | Path |
+|---|---|
+| `ChatListView.kt` | `views/chatlist/ChatListView.kt` |
+| `ChatListNavLinkView.kt` | `views/chatlist/ChatListNavLinkView.kt` |
+| `ChatPreviewView.kt` | `views/chatlist/ChatPreviewView.kt` |
+| `UserPicker.kt` | `views/chatlist/UserPicker.kt` |
+| `TagListView.kt` | `views/chatlist/TagListView.kt` |
diff --git a/apps/multiplatform/product/views/chat.md b/apps/multiplatform/product/views/chat.md
new file mode 100644
index 0000000000..64abda7ee6
--- /dev/null
+++ b/apps/multiplatform/product/views/chat.md
@@ -0,0 +1,135 @@
+# Chat View (Conversation)
+
+> **Related spec:** [spec/client/chat-view.md](../../spec/client/chat-view.md)
+
+## Purpose
+
+Full conversation view for displaying and interacting with messages in a direct contact chat, group chat, or note-to-self. Supports text messaging with markdown, media attachments, voice messages, E2E encrypted calls, message reactions, replies, forwarding, reporting, and content search/filtering.
+
+## Route / Navigation
+
+- **Entry point**: Tap a chat row in `ChatListView` (routed by `ChatListNavLinkView`)
+- **Presented by**: `ChatView` composable bound to `chatModel.chatId`; on Desktop, shown in the center column
+- **Back navigation**: Sets `chatModel.chatId = null`, stops `AudioPlayer`, clears group members, returns to chat list
+- **Sub-navigation**:
+ - Info button opens `ChatInfoView` (contact) or `GroupChatInfoView` (group) via `ModalManager.end`
+ - Member avatars in group chats navigate to `GroupMemberInfoView`
+ - Reports button opens `GroupReportsView` for groups with moderation reports
+ - Support chats button opens `MemberSupportView` (moderators) or member support chat (regular members)
+
+## Page Sections
+
+### Navigation Bar (`ChatLayout`)
+
+Custom toolbar with themed background:
+
+| Element | Description |
+|---|---|
+| Back button | Returns to chat list; stops audio/video playback |
+| Contact/Group avatar | Small profile image in toolbar |
+| Chat name | Display name; tappable to open info view |
+| Verified shield | Shows verified contact checkmark (direct chats with verified contacts only) |
+| More menu button | Opens overflow menu containing search and audio/video call buttons (call buttons shown in direct chats only) |
+| Info button | Opens `ChatInfoView` (direct) or `GroupChatInfoView` (group) |
+| Reports count | Badge for group reports count; taps open reports view |
+| Support chats | Badge for member support; taps open support chat view |
+
+### Message List
+
+Rendered by `LazyColumnWithScrollBar` with pagination:
+
+| Feature | Description |
+|---|---|
+| Scroll direction | Bottom-to-top (newest messages at bottom) |
+| Pagination | `apiLoadMessages` called on scroll to load more; supports `.before`, `.after`, `.around`, `.initial` |
+| Merged items | Adjacent messages grouped with `ItemSeparation` (timestamp, large gap, date separators) |
+| Floating buttons | Scroll-to-bottom button with unread count |
+| Date separators | Date headers between messages from different days |
+| Wallpaper | Per-chat themed background via `perChatTheme` from contact/group `uiThemes` |
+| Content filter | Filter messages by type via `ContentFilter` (images, files, links, etc.) |
+
+### Message Types
+
+Each type has a dedicated composable in `views/chat/item/`:
+
+| Type | Composable | Description |
+|---|---|---|
+| Text | `FramedItemView` | Rendered with markdown (bold, italic, code, links, `@mentions`) via `CIMarkdownText` |
+| Image | `CIImageView` | Thumbnail with tap-to-fullscreen via `ImageFullScreenView` |
+| Video | `CIVideoView` | Video thumbnail with play button; inline playback via `VideoPlayerHolder` |
+| Voice | `CIVoiceView` | Waveform visualization with playback controls and duration |
+| File | `CIFileView` | File icon, name, size; download/open actions with progress indicator |
+| Link preview | `ChatItemLinkView` | URL preview card with title, description, image (defined in `LinkPreviews.kt`) |
+| Emoji-only | `EmojiItemView` | Large emoji rendering without message bubble |
+| Call event | `CICallItemView` | Call status (missed, ended, duration) |
+| Group event | `CIEventView` | Member joined/left, role changes, group updates |
+| E2EE info | `CIChatFeatureView` | Encryption status and feature change notifications |
+| Group invitation | `CIGroupInvitationView` | Inline group join invitation card |
+| Deleted | `DeletedItemView` / `MarkedDeletedItemView` | Placeholder for deleted messages |
+| Decryption error | `CIRcvDecryptionError` | Error with ratchet sync suggestion |
+| Invalid JSON | `CIInvalidJSONView` | Developer fallback for malformed items |
+| Integrity error | `IntegrityErrorItemView` | Message integrity/gap warnings |
+
+### Message Interactions
+
+Long-press context menu on any message:
+
+| Action | Description |
+|---|---|
+| Reply | Sets compose bar to reply mode with quoted message (`ComposeContextItem.QuotedItem`) |
+| Forward | Opens destination picker; uses `apiPlanForwardChatItems` with confirmation for partial forwards |
+| Copy | Copies message text to clipboard |
+| Edit | Enters edit mode (`ComposeContextItem.EditingItem`); own messages within edit window |
+| Delete | Delete for self or delete for everyone (with confirmation via `deleteMessagesAlertDialog`) |
+| Moderate | Group moderators can delete messages for all members (`moderateMessagesAlertDialog`) |
+| React | Emoji reaction picker |
+| Report | Report message to group moderators (`ComposeContextItem.ReportedItem` with `ReportReason`) |
+| Select multiple | Enters multi-select mode (`selectedChatItems`) with bulk delete/forward/archive toolbar |
+| Archive | Archive selected reports (moderators) |
+
+### Compose Bar (`ComposeView` + `SendMsgView`)
+
+Bottom input area for composing messages:
+
+| Element | Description |
+|---|---|
+| Text field | `PlatformTextField` with markdown support, `@mention` autocomplete, file paste support |
+| Attachment button | Opens `ModalBottomSheetLayout` with options: camera, gallery (image/video), file |
+| Send button | Sends message; changes to checkmark for reports; animated size/alpha |
+| Voice record button | Shown when text is empty and voice allowed; hold to record, release to preview |
+| Live message button | Start/update live typing message (if `liveMessageAlertShown`) |
+| Context preview | Shows quoted message, editing indicator, or forwarding source above text field |
+| Media preview | Thumbnail row of selected images/videos before sending |
+| Link preview | Auto-generated link preview card (`ComposePreview.CLinkPreview`) |
+| Connecting status | "Connecting..." text shown when contact is not yet ready |
+| Commands menu | Developer commands (`showCommandsMenu`) |
+
+Compose states (`ComposeState`):
+- `NoContextItem` -- normal new message
+- `QuotedItem` -- replying to a message
+- `EditingItem` -- editing own message
+- `ForwardingItems` -- forwarding from another chat
+- `ReportedItem` -- reporting a message with reason
+
+### Multi-Select Toolbar (`SelectedItemsButtonsToolbar`)
+
+Shown when `selectedChatItems != null`:
+
+| Button | Description |
+|---|---|
+| Delete / Archive | Delete selected messages (for self, or for everyone if allowed by `fullDeleteAllowed`); shown as Archive for report items (group moderators only) |
+| Forward | Forward selected messages to another chat |
+| Moderate | Delete selected messages for all members (group moderators only) |
+
+### Timed/Disappearing Messages
+
+When `timedMessageAllowed` is true, compose bar includes a timer icon for setting message disappear time via `customDisappearingMessageTimePref`.
+
+## Source Files
+
+| File | Path |
+|---|---|
+| `ChatView.kt` | `views/chat/ChatView.kt` |
+| `ComposeView.kt` | `views/chat/ComposeView.kt` |
+| `SendMsgView.kt` | `views/chat/SendMsgView.kt` |
+| Chat item views | `views/chat/item/*.kt` |
diff --git a/apps/multiplatform/product/views/contact-info.md b/apps/multiplatform/product/views/contact-info.md
new file mode 100644
index 0000000000..32793a3b70
--- /dev/null
+++ b/apps/multiplatform/product/views/contact-info.md
@@ -0,0 +1,104 @@
+# Contact Info
+
+> **Related spec:** [spec/client/chat-view.md](../../spec/client/chat-view.md)
+
+## Purpose
+
+View contact details, manage per-contact preferences, verify security codes for E2E encryption, manage connection settings (switch address, sync ratchet), and perform destructive actions like clearing or deleting a contact.
+
+## Route / Navigation
+
+- **Entry point**: Tap the info button in `ChatView` navigation bar (when viewing a direct contact chat)
+- **Presented by**: `ChatInfoView` composable shown via `ModalManager.end` from `ChatView`
+- **Sub-navigation**:
+ - Contact preferences -> `ContactPreferencesView` (via `ModalManager.end`)
+ - Security code verification -> `VerifyCodeView` (via `ModalManager.end`)
+ - Chat wallpaper -> wallpaper editor
+ - Group profile view (for group-direct contacts)
+
+## Page Sections
+
+### Contact Info Header
+
+| Element | Description |
+|---|---|
+| Profile image | Large circular avatar (tappable) |
+| Display name | Contact's display name |
+| Full name | Optional full name below display name |
+| Connection status | Shows if contact is ready, connecting, or has issues |
+
+### Local Alias
+
+Editable text field for setting a local-only name visible only on this device. Not shared with the contact. Changes saved via `setContactAlias()`.
+
+### Action Buttons
+
+Horizontal row of quick-action buttons:
+
+| Button | Description |
+|---|---|
+| Search | Triggers `onSearchClicked` to search messages in chat |
+| Audio call | Initiate audio call |
+| Video call | Initiate video call |
+| Mute/Unmute | Toggle notification mode |
+
+### Incognito Section
+
+Shown only when `customUserProfile` is set (connected via incognito profile):
+
+| Element | Description |
+|---|---|
+| Incognito icon | Indicates incognito connection |
+| Profile name | The random profile name used for this connection |
+
+### Chat Preferences
+
+| Setting | Description |
+|---|---|
+| Send receipts | Per-contact delivery receipt setting (`SendReceipts` tristate: default/on/off) |
+| Chat item TTL | Per-contact message retention setting (`ChatItemTTL` with alert confirmation) |
+| Contact preferences | Opens `ContactPreferencesView` for feature toggles (timed messages, full delete, reactions, voice, calls) |
+
+### Connection Details
+
+Shown when `connectionStats` is available:
+
+| Element | Description |
+|---|---|
+| Connection stats | Server information, agent connection ID |
+| Switch address | Initiates SMP server address switch (`apiSwitchContact`) with confirmation alert |
+| Abort switch | Cancels an in-progress address switch (`apiAbortSwitchContact`) |
+| Sync connection | Fixes encryption ratchet synchronization (`apiSyncContactRatchet`) |
+| Force sync | Force ratchet re-synchronization with confirmation alert |
+
+### Security Code Verification
+
+| Element | Description |
+|---|---|
+| Verify button | Opens `VerifyCodeView` showing the connection security code |
+| Verified badge | Shows checkmark when contact is verified |
+| Code comparison | Side-by-side code display for out-of-band verification via `apiVerifyContact` |
+
+### Developer Tools Section
+
+Shown when `developerTools` preference is enabled:
+
+| Element | Description |
+|---|---|
+| Database ID | Contact's internal database identifier |
+| Agent connection ID | Underlying SMP agent connection ID |
+
+### Destructive Actions
+
+| Action | Description |
+|---|---|
+| Clear chat | Deletes all messages in chat (with confirmation via `clearChatDialog`) |
+| Delete contact | Removes the contact and all associated data (with confirmation via `deleteContactDialog`) |
+
+## Source Files
+
+| File | Path |
+|---|---|
+| `ChatInfoView.kt` | `views/chat/ChatInfoView.kt` |
+| `ContactPreferences.kt` | `views/chat/ContactPreferences.kt` |
+| `VerifyCodeView.kt` | `views/chat/VerifyCodeView.kt` |
diff --git a/apps/multiplatform/product/views/group-info.md b/apps/multiplatform/product/views/group-info.md
new file mode 100644
index 0000000000..65b068adc8
--- /dev/null
+++ b/apps/multiplatform/product/views/group-info.md
@@ -0,0 +1,145 @@
+# Group Chat Info
+
+> **Related spec:** [spec/client/chat-view.md](../../spec/client/chat-view.md)
+
+## Purpose
+
+View and manage group settings, member list, group preferences, group links, member admission, welcome messages, and moderation features. The scope of available actions depends on the user's role within the group (member, moderator, admin, owner).
+
+## Route / Navigation
+
+- **Entry point**: Tap the info button in `ChatView` navigation bar (when viewing a group chat)
+- **Presented by**: `GroupChatInfoView` composable shown via `ModalManager.end` from `ChatView`
+- **Sub-navigation**:
+ - Edit group profile -> `GroupProfileView` (via `ModalManager.end`)
+ - Add members -> `AddGroupMembersView` (via `ModalManager.end`)
+ - Group link -> `GroupLinkView` (via `ModalManager.end`)
+ - Group preferences -> `GroupPreferencesView` (via `ModalManager.end`)
+ - Welcome message -> `GroupWelcomeView` (via `ModalManager.end`)
+ - Member info -> `GroupMemberInfoView` (via `ModalManager.end`)
+ - Chat wallpaper -> wallpaper editor
+ - Member support -> `MemberSupportView` (via `ModalManager.end`)
+
+## Page Sections
+
+### Group Info Header
+
+| Element | Description |
+|---|---|
+| Group image | Large circular profile image |
+| Group name | Display name (editable by owners via `GroupProfileView`) |
+| Member count | "N members" label from `activeSortedMembers` |
+| Full name | Optional secondary name |
+| Description | Group description text (if set) |
+
+### Local Alias
+
+Editable text field for a local-only alias (not shared with other members). Changes saved via `setGroupAlias()`.
+
+### Action Buttons
+
+Horizontal row of action buttons:
+
+| Button | Description |
+|---|---|
+| Search | Triggers `onSearchClicked` callback to search messages in chat |
+| Mute/Unmute | Toggle notification mode |
+| Add members | Opens `AddGroupMembersView` (shown when user has admin+ role and `groupInfo.canAddMembers`) |
+
+### Group Management Section
+
+Available actions depend on role (`GroupMemberRole`):
+
+| Action | Minimum Role | Description |
+|---|---|---|
+| Edit group profile | Owner | Opens `GroupProfileView` to edit name, image, description |
+| Add members | Admin | Opens `AddGroupMembersView` to invite contacts |
+| Manage group link | Admin | Opens `GroupLinkView` to create/share/delete group link |
+| Member support | Moderator | Opens `MemberSupportView` to manage member support chats |
+| Edit welcome message | Owner | Opens `GroupWelcomeView` to set the auto-sent welcome text |
+| Group preferences | Any | Opens `GroupPreferencesView` (read-only; only owners can change settings) |
+
+### Chat Preferences
+
+| Setting | Description |
+|---|---|
+| Send receipts | Per-group delivery receipt setting (`SendReceipts`); limited to groups under `SMALL_GROUPS_RCPS_MEM_LIMIT` (20 members) |
+| Chat item TTL | Per-group message retention setting with confirmation alert via `setChatTTLAlert` |
+
+### Member List
+
+Displays `activeSortedMembers` (excluding left/removed members, sorted by role descending):
+
+| Element | Description |
+|---|---|
+| Member avatar | `MEMBER_ROW_AVATAR_SIZE` (42dp) profile image |
+| Member name | Display name with role badge |
+| Member role | Owner, Admin, Moderator, Member, Observer |
+| Member status | Active, connecting, pending, left, removed |
+| Tap action | Opens `GroupMemberInfoView` with connection stats and verification code |
+
+### Group Link (`GroupLinkView`)
+
+| Element | Description |
+|---|---|
+| Create link button | `apiCreateGroupLink` generates a shareable group invitation link |
+| QR code display | QR code rendering of the group link |
+| Short link toggle | Switch between short and full link display |
+| Share button | System share for the link |
+| Copy button | Copy link to clipboard |
+| Member role selector | Set the default role for members joining via link (`acceptMemberRole`) |
+| Add short link | `apiAddGroupShortLink` creates a short link that includes group profile |
+| Delete link | Remove the group link with confirmation |
+
+### Add Members (`AddGroupMembersView`)
+
+| Element | Description |
+|---|---|
+| Contact list | Filterable list of contacts to invite |
+| Role selector | Set the role for invited members |
+| Invite button | Sends group invitations to selected contacts |
+| Group link option | Alternative to direct invitation |
+
+### Group Member Info (`GroupMemberInfoView`)
+
+| Element | Description |
+|---|---|
+| Member profile | Avatar, name, role |
+| Connection stats | Server information, connection status |
+| Security code | Verification code for the member connection |
+| Role change | Change member role (admin+ only) |
+| Remove member | Remove from group (admin+ only) |
+| Block member | Block member for self |
+| Direct message | Open direct chat with member |
+
+### Developer Tools Section
+
+Shown when `developerTools` preference is enabled:
+
+| Element | Description |
+|---|---|
+| Database ID | Group's internal database identifier |
+
+### Destructive Actions
+
+| Action | Condition | Description |
+|---|---|---|
+| Clear chat | Any member | Deletes all messages locally (`clearChatDialog`) |
+| Leave group | Non-owner | Leave the group (`leaveGroupDialog`) |
+| Delete group | Owner or non-current member | Delete group for all (owner) or for self (`deleteGroupDialog`) |
+
+Business chats use alternative labels: "Delete chat" instead of "Delete group".
+
+## Source Files
+
+| File | Path |
+|---|---|
+| `GroupChatInfoView.kt` | `views/chat/group/GroupChatInfoView.kt` |
+| `GroupMemberInfoView.kt` | `views/chat/group/GroupMemberInfoView.kt` |
+| `AddGroupMembersView.kt` | `views/chat/group/AddGroupMembersView.kt` |
+| `GroupLinkView.kt` | `views/chat/group/GroupLinkView.kt` |
+| `GroupProfileView.kt` | `views/chat/group/GroupProfileView.kt` |
+| `GroupPreferences.kt` | `views/chat/group/GroupPreferences.kt` |
+| `WelcomeMessageView.kt` | `views/chat/group/WelcomeMessageView.kt` |
+| `MemberAdmission.kt` | `views/chat/group/MemberAdmission.kt` |
+| `MemberSupportView.kt` | `views/chat/group/MemberSupportView.kt` |
diff --git a/apps/multiplatform/product/views/new-chat.md b/apps/multiplatform/product/views/new-chat.md
new file mode 100644
index 0000000000..b664fda67f
--- /dev/null
+++ b/apps/multiplatform/product/views/new-chat.md
@@ -0,0 +1,96 @@
+# New Chat / Connection
+
+> **Related spec:** [spec/client/navigation.md](../../spec/client/navigation.md)
+
+## Purpose
+
+Create new contacts, groups, or connect with others via one-time invitation links or by scanning/pasting SimpleX links. This is the primary entry point for establishing new E2E encrypted connections.
+
+## Route / Navigation
+
+- **Entry point**: Tap the new chat button (pencil icon) in `ChatListView` toolbar or FAB
+- **Presented by**: `NewChatSheet` modal from `ChatListView` via `showNewChatSheet()`; wraps `NewChatView` and group creation in `ModalManager.start`
+- **Internal navigation**: `NewChatSheet` provides 3 action buttons:
+ - "Create 1-time link" -- opens `NewChatView` with `INVITE` tab (generate and share a one-time invitation link)
+ - "Scan / paste link" -- opens `NewChatView` with `CONNECT` tab (scan QR code or paste a received link)
+ - "Create group" -- opens `AddGroupView`
+- **Tabs within NewChatView**: `HorizontalPager` with `TabRow` toggles between `NewChatOption.INVITE` (1-time link) and `NewChatOption.CONNECT` (connect via link)
+- **Swipe gesture**: Left/right swipe switches between tabs (Android only; `userScrollEnabled = appPlatform.isAndroid`)
+- **Dismiss behavior**: On dispose, a `DisposableEffect` shows an alert dialog (via `AlertManager.shared.showAlertDialog`) asking whether to keep an unused invitation link or delete it via `controller.deleteChat()`
+
+## Page Sections
+
+### Tab Selector
+
+| Tab | Icon | Label | Description |
+|---|---|---|---|
+| 1-time link | `ic_repeat_one` | "1-time link" | Generate and share a one-time invitation link |
+| Connect via link | `ic_qr_code` | "Connect via link" | Scan QR code or paste a received link |
+
+### Invite Tab (1-time Link) -- `PrepareAndInviteView`
+
+Displayed when `selection == INVITE`:
+
+| Element | Description |
+|---|---|
+| QR code display | Generated QR code for the invitation link (`SimpleXLinkQRCode`) |
+| Short/full link toggle | Switch between short and full link display |
+| Share button | System share for the invitation link |
+| Copy button | Copy link to clipboard |
+| Incognito toggle | Option to connect with a random profile |
+| Loading state | `CreatingLinkProgressView` with "Creating link" text while `creatingConnReq` is true |
+| Retry button | `RetryButton` shown if link creation fails; calls `createInvitation()` |
+
+Link creation calls `apiAddContact` which returns a `CreatedConnLink` with both `connFullLink` and optional `connShortLink`. The invitation is tracked via `chatModel.showingInvitation`.
+
+### Connect Tab -- `ConnectView`
+
+Displayed when `selection == CONNECT`:
+
+| Element | Description |
+|---|---|
+| QR code scanner | Camera-based QR code scanner (`showQRCodeScanner` state) |
+| Paste link field | Text field for pasting a SimpleX link (`pastedLink`) |
+| Connect button | Initiates connection via `planAndConnect()` |
+
+When a valid SimpleX link is detected:
+1. `planAndConnect()` is called with the link URI
+2. If the link matches a known contact, filters to that chat
+3. If the link matches a known group, filters to that group
+4. Otherwise, creates a new connection
+
+### Create Group (`AddGroupView`)
+
+| Element | Description |
+|---|---|
+| Group name field | Required display name input with `FocusRequester` |
+| Profile image picker | `GetImageBottomSheet` for selecting/cropping a group avatar |
+| Incognito toggle | Option to create group with random profile (`incognitoPref`) |
+| Create button | Calls `apiNewGroup()`, then opens `AddGroupMembersView` (normal) or `GroupLinkView` (incognito) |
+
+Group creation flow:
+1. User enters group name and optionally selects an image
+2. `apiNewGroup()` creates the group and returns `GroupInfo`
+3. `openGroupChat()` navigates to the new group chat
+4. `setGroupMembers()` preloads member data
+5. `AddGroupMembersView` opens for inviting contacts (or `GroupLinkView` for incognito groups)
+
+### QR Code Components (`QRCode.kt`)
+
+| Component | Description |
+|---|---|
+| `SimpleXLinkQRCode` | Renders a QR code for a SimpleX connection link |
+| QR scanner | Platform camera scanner for reading QR codes |
+| Short link display | Compact link text with copy/share actions |
+
+## Source Files
+
+| File | Path |
+|---|---|
+| `NewChatView.kt` | `views/newchat/NewChatView.kt` |
+| `AddGroupView.kt` | `views/newchat/AddGroupView.kt` |
+| `QRCode.kt` | `views/newchat/QRCode.kt` |
+| `NewChatSheet.kt` | `views/newchat/NewChatSheet.kt` |
+| `ConnectPlan.kt` | `views/newchat/ConnectPlan.kt` |
+| `QRCodeScanner.kt` | `views/newchat/QRCodeScanner.kt` (expect/actual) |
+| `ContactConnectionInfoView.kt` | `views/newchat/ContactConnectionInfoView.kt` |
diff --git a/apps/multiplatform/product/views/onboarding.md b/apps/multiplatform/product/views/onboarding.md
new file mode 100644
index 0000000000..4127ac65f7
--- /dev/null
+++ b/apps/multiplatform/product/views/onboarding.md
@@ -0,0 +1,139 @@
+# Onboarding
+
+> **Related spec:** [spec/client/navigation.md](../../spec/client/navigation.md)
+
+## Purpose
+
+First-time setup flow for new users. Guides through app introduction, profile creation, database passphrase setup (Desktop), server operator conditions acceptance, SimpleX address creation, and notification configuration (Android). Also provides an entry point for device migration.
+
+## Route / Navigation
+
+- **Entry point**: App launch when `onboardingStage` is not `OnboardingComplete`
+- **Presented by**: `OnboardingView` renders the appropriate step based on `OnboardingStage` enum
+- **Flow direction**: Linear progression controlled by `appPrefs.onboardingStage`
+- **Completion**: Sets `onboardingStage` to `OnboardingComplete`
+
+## Onboarding Stages
+
+The `OnboardingStage` enum defines the flow:
+
+| Stage | Description |
+|---|---|
+| `Step1_SimpleXInfo` | Welcome screen with app introduction |
+| `Step2_CreateProfile` | Create first user profile |
+| `LinkAMobile` | Desktop-only: link a mobile device |
+| `Step2_5_SetupDatabasePassphrase` | Desktop-only: set database encryption passphrase |
+| `Step3_ChooseServerOperators` | Accept server operator conditions |
+| `Step3_CreateSimpleXAddress` | Create a SimpleX contact address |
+| `Step4_SetNotificationsMode` | Android-only: configure notification mode |
+| `OnboardingComplete` | Onboarding finished |
+
+## Page Sections
+
+### Step 1: Welcome / SimpleX Info (`SimpleXInfo`)
+
+**Stage**: `Step1_SimpleXInfo`
+
+| Element | Description |
+|---|---|
+| Logo | `SimpleXLogo` -- SimpleX Chat logo (light/dark variant based on `isInDarkTheme()`) |
+| Info button | `OnboardingInformationButton` -- "The next generation of private messaging"; taps open `HowItWorks` fullscreen modal |
+| Privacy redefined | `InfoRow` with privacy icon: "No user identifiers" |
+| Immune to spam | `InfoRow` with shield icon: "You decide who can connect" |
+| Decentralized | `InfoRow` with decentralized icon: "Anybody can host servers" |
+| **Create your profile** button | `OnboardingActionButton` -- primary action; advances to profile creation |
+| **Migrate from another device** button | `TextButtonBelowOnboardingButton` -- opens `MigrateToDeviceView` fullscreen modal |
+
+Layout: `ColumnWithScrollBar` with `DEFAULT_ONBOARDING_HORIZONTAL_PADDING`, max width constrained (250dp Android, 500dp Desktop).
+
+### Step 2: Create Profile
+
+**Stage**: `Step2_CreateProfile`
+
+| Element | Description |
+|---|---|
+| Display name field | Required text input; auto-focused |
+| Validation | Name validation with `mkValidName` check |
+| Create button | Creates profile via API; advances to next step |
+
+Profile is stored locally and only shared with contacts.
+
+### Step 2.5: Setup Database Passphrase (Desktop only)
+
+**Stage**: `Step2_5_SetupDatabasePassphrase`
+
+| Element | Description |
+|---|---|
+| Passphrase field | Secure text input for database encryption key |
+| Confirm field | Passphrase confirmation |
+| Set button | Encrypts database with passphrase |
+
+### Link a Mobile (Desktop only)
+
+**Stage**: `LinkAMobile`
+
+| Element | Description |
+|---|---|
+| Instructions | How to connect mobile device to desktop |
+| QR code | Connection QR code for mobile scanning |
+| Skip button | Skip this step |
+
+### Step 3: Choose Server Operators
+
+**Stage**: `Step3_ChooseServerOperators`
+
+| Element | Description |
+|---|---|
+| Operator list | Available server operators with conditions |
+| Conditions text | Terms of service for selected operators |
+| Accept button | Accept conditions and continue |
+
+Managed by `ChooseServerOperators.kt`.
+
+### Step 3b: Create SimpleX Address
+
+**Stage**: `Step3_CreateSimpleXAddress`
+
+| Element | Description |
+|---|---|
+| Address creation | Auto-creates a SimpleX contact address |
+| QR code | Displays the created address as QR code |
+| Share button | Share address link |
+| Skip button | Skip address creation |
+
+### Step 4: Set Notifications Mode (Android only)
+
+**Stage**: `Step4_SetNotificationsMode`
+
+| Element | Description |
+|---|---|
+| Notification options | Instant (background service) / Periodic (every 10 min) / Off |
+| Description | Explains battery impact and notification behavior for each mode |
+| Continue button | Saves selection and completes onboarding |
+
+Managed by `SetNotificationsMode.kt`.
+
+### What's New (`WhatsNewView`)
+
+Shown after onboarding or when triggered from Settings:
+
+| Element | Description |
+|---|---|
+| Version highlights | New features and changes in the current version |
+| Updated conditions | Notice about updated server operator conditions (if applicable) |
+| Close button | Dismisses the view |
+
+Triggered in `ChatListView` via `shouldShowWhatsNew()` with a 1-second delay.
+
+## Source Files
+
+| File | Path |
+|---|---|
+| `OnboardingView.kt` | `views/onboarding/OnboardingView.kt` |
+| `SimpleXInfo.kt` | `views/onboarding/SimpleXInfo.kt` |
+| `HowItWorks.kt` | `views/onboarding/HowItWorks.kt` |
+| `SetupDatabasePassphrase.kt` | `views/onboarding/SetupDatabasePassphrase.kt` |
+| `SetNotificationsMode.kt` | `views/onboarding/SetNotificationsMode.kt` |
+| `ChooseServerOperators.kt` | `views/onboarding/ChooseServerOperators.kt` |
+| `WhatsNewView.kt` | `views/onboarding/WhatsNewView.kt` |
+| `LinkAMobileView.kt` | `views/onboarding/LinkAMobileView.kt` |
diff --git a/apps/multiplatform/product/views/settings.md b/apps/multiplatform/product/views/settings.md
new file mode 100644
index 0000000000..e668bf2d04
--- /dev/null
+++ b/apps/multiplatform/product/views/settings.md
@@ -0,0 +1,159 @@
+# Settings
+
+> **Related spec:** [spec/client/navigation.md](../../spec/client/navigation.md) | [spec/services/theme.md](../../spec/services/theme.md) | [spec/services/notifications.md](../../spec/services/notifications.md)
+
+## Purpose
+
+Configure all aspects of app behavior including notifications, network/servers, privacy, appearance, database management, call settings, and developer tools. Accessed from the UserPicker or directly from the chat list toolbar.
+
+## Route / Navigation
+
+- **Entry point**: Tap user avatar in `ChatListView` toolbar -> `UserPicker` -> Settings option; or directly via `NavigationButtonMenu` when no users exist
+- **Presented by**: `SettingsView` composable via `ModalManager.start.showModalCloseable`
+- **Navigation title**: "Your settings" (`AppBarTitle`)
+- **Sub-navigation**: Each settings row opens a dedicated view via `showSettingsModal` or `showCustomModal`
+
+## Platform Differences
+
+| Aspect | Android | Desktop |
+|---|---|---|
+| App section | Device settings, app version | App updates (`AppUpdater`), device settings, app version |
+| Notifications | Full notification mode selection (instant/periodic/off) | Notification settings |
+| Use from desktop/mobile | "Use from desktop" option in UserPicker | "Link a mobile" / "Linked mobiles" option in UserPicker |
+| Database migration | "Migrate to another device" with auth | Same |
+
+## Page Sections
+
+### Settings Section
+
+| Row | Icon | Destination | Description |
+|---|---|---|---|
+| Notifications | `ic_bolt` / `ic_bolt_off` | `NotificationsSettingsView` | Push notification mode and preview settings |
+| Network & servers | `ic_wifi_tethering` | `NetworkAndServersView` | SMP/XFTP servers, proxy, .onion hosts, advanced network |
+| Audio & video calls | `ic_videocam` | `CallSettingsView` | WebRTC relay policy, ICE servers |
+| Privacy & security | `ic_lock` | `PrivacySettingsView` | SimpleX Lock, delivery receipts, link previews, auto-accept |
+| Appearance | `ic_light_mode` | `AppearanceView` | Theme, language, profile images, chat bubbles |
+
+All rows disabled when `chatModel.chatRunning != true` (except Appearance).
+
+#### Notifications (`NotificationsSettingsView`)
+
+| Setting | Options |
+|---|---|
+| Notification mode | Instant (background service) / Periodic (every 10 min) / Off |
+| Notification preview | Configuration for notification content visibility |
+
+#### Network & Servers (`NetworkAndServersView`)
+
+| Setting | Description |
+|---|---|
+| SMP servers | Messaging relay servers; per-operator configuration |
+| XFTP servers | File transfer servers; per-operator configuration |
+| Server operators | `OperatorView` for each configured operator |
+| Advanced network | `AdvancedNetworkSettings` -- timeouts, TCP keep-alive, reconnect intervals |
+| Proxy configuration | SOCKS proxy, .onion host settings |
+
+Sub-files: `NetworkAndServers.kt`, `ProtocolServersView.kt`, `ProtocolServerView.kt`, `NewServerView.kt`, `ScanProtocolServer.kt`, `AdvancedNetworkSettings.kt`, `OperatorView.kt`
+
+#### Audio & Video Calls (`CallSettingsView`)
+
+| Setting | Description |
+|---|---|
+| WebRTC relay policy | Always relay / relay when needed / never relay |
+| ICE servers | Custom STUN/TURN server configuration |
+
+#### Privacy & Security (`PrivacySettingsView`)
+
+Organized in sections:
+
+**Device Section** (`PrivacyDeviceSection`):
+
+| Setting | Description |
+|---|---|
+| SimpleX Lock | `SimplexLockView` -- app lock with system auth or passcode (`LAMode.SYSTEM` / `LAMode.PASSCODE`) |
+
+**Chats Section**:
+
+| Setting | Preference Key | Description |
+|---|---|---|
+| Send link previews | `privacyLinkPreviews` | Auto-generate link preview cards |
+| Sanitize links | `privacySanitizeLinks` | Strip tracking parameters from URLs |
+| Show last messages | `privacyShowChatPreviews` | Show message previews in chat list |
+| Message draft | `privacySaveLastDraft` | Save unsent message draft for each chat |
+
+**Files Section**:
+
+| Setting | Preference Key | Description |
+|---|---|---|
+| Encrypt local files | `privacyEncryptLocalFiles` | Encrypt files stored on device |
+| Auto-accept images | `privacyAcceptImages` | Automatically download received images |
+| Blur media radius | `privacyMediaBlurRadius` | Blur radius for media previews |
+| Protect IP address | `privacyAskToApproveRelays` | Prompt before connecting to unknown file relays to protect IP address |
+
+#### Appearance (`AppearanceView`)
+
+Platform-specific composable (`expect fun AppearanceView`):
+
+| Setting | Description |
+|---|---|
+| Profile images | `ProfileImageSection` -- slider for profile image corner radius |
+| Theme selection | Color scheme / theme picker |
+| Language | App language selection |
+| Chat wallpaper | Background image settings |
+| Chat bubbles | Message bubble appearance configuration |
+| Toolbar opacity | App bar transparency settings (`inAppBarsAlpha`) |
+| Color picker | `ClassicColorPicker` for custom theme colors |
+
+### Chat Database Section
+
+| Row | Icon | Destination | Description |
+|---|---|---|---|
+| Database passphrase & export | `ic_database` | `DatabaseView` | Manage encryption, export/import database |
+| Migrate to another device | `ic_ios_share` | `MigrateFromDeviceView` | Device migration (requires auth) |
+
+Database icon shows warning color (`WarningOrange`) when database is not encrypted or passphrase is not saved.
+
+### Help Section
+
+| Row | Icon | Destination | Description |
+|---|---|---|---|
+| How to use SimpleX Chat | `ic_help` | `HelpView` | Usage guide |
+| What's new | `ic_add` | `WhatsNewView` | Version changelog |
+| About SimpleX Chat | `ic_info` | `SimpleXInfo` (non-onboarding mode) | App information |
+| Chat with the founder | `ic_tag` | Opens SimpleX link | Direct chat with SimpleX team |
+| Send us an email | `ic_mail` | Opens mailto: | Email support |
+
+### Support Section
+
+| Row | Icon | Description |
+|---|---|---|
+| Contribute | `ic_keyboard` | Opens GitHub contribution page (hidden for Android Bundle) |
+| Rate the app | `ic_star` | Opens Google Play / app store listing |
+| Star on GitHub | `ic_github` | Opens GitHub repository |
+
+### App Section (`SettingsSectionApp`)
+
+Platform-specific section (expect/actual composable):
+
+| Row | Description |
+|---|---|
+| App updates (Desktop) | App update checker and installer |
+| Developer tools | Toggle developer mode |
+| Chat console | Opens `ChatConsoleView` terminal |
+| Terminal always visible (Desktop) | Keep terminal window open |
+| Install terminal app | Link to CLI app on GitHub |
+| Reset all hints | Reset dismissed hint/card preferences |
+| App version | Version string with build info; taps open `VersionInfoView` |
+
+## Source Files
+
+| File | Path |
+|---|---|
+| `SettingsView.kt` | `views/usersettings/SettingsView.kt` |
+| `Appearance.kt` | `views/usersettings/Appearance.kt` |
+| `PrivacySettings.kt` | `views/usersettings/PrivacySettings.kt` |
+| `NetworkAndServers.kt` | `views/usersettings/networkAndServers/NetworkAndServers.kt` |
+| `AdvancedNetworkSettings.kt` | `views/usersettings/networkAndServers/AdvancedNetworkSettings.kt` |
+| `OperatorView.kt` | `views/usersettings/networkAndServers/OperatorView.kt` |
+| `ProtocolServersView.kt` | `views/usersettings/networkAndServers/ProtocolServersView.kt` |
+| `NewServerView.kt` | `views/usersettings/networkAndServers/NewServerView.kt` |
diff --git a/apps/multiplatform/product/views/user-profiles.md b/apps/multiplatform/product/views/user-profiles.md
new file mode 100644
index 0000000000..dfc37a5e8d
--- /dev/null
+++ b/apps/multiplatform/product/views/user-profiles.md
@@ -0,0 +1,122 @@
+# User Profiles
+
+> **Related spec:** [spec/client/navigation.md](../../spec/client/navigation.md)
+
+## Purpose
+
+Manage multiple chat profiles within a single app instance. Users can create, switch between, hide, mute, and delete profiles. Hidden profiles are protected by password. The UserPicker provides quick profile switching from the chat list, while UserProfilesView offers full profile management.
+
+## Route / Navigation
+
+- **Entry point**: Tap user avatar in `ChatListView` toolbar -> `UserPicker` -> "Your chat profiles"
+- **Presented by**: `UserProfilesView` composable via `ModalManager.start.showCustomModal` with search bar
+- **Navigation title**: "Your chat profiles" (`AppBarTitle`)
+- **Sub-navigation**:
+ - Create profile -> `CreateProfile` (via `ModalManager.center`)
+ - Edit active profile -> `UserProfileView` (via UserPicker tap on active user)
+ - User address -> `UserAddressView` (via UserPicker)
+ - Chat preferences -> `PreferencesView` (via UserPicker)
+
+## Page Sections
+
+### UserPicker (`UserPicker.kt`)
+
+Overlay panel triggered from `ChatListView` toolbar:
+
+| Section | Description |
+|---|---|
+| Device picker row | `DevicePickerRow` showing local device and connected remote hosts (Desktop only); pill-shaped buttons with connect/disconnect actions |
+| Active user profile | `ProfilePreview` of current user (Desktop: single row; Android: full user list) |
+| User list | `UserPickerUsersSection` with all visible non-hidden profiles; tap to switch, long-press disabled |
+| SimpleX address | Row to open `UserAddressView` (create or view address) |
+| Chat preferences | Row to open `PreferencesView` |
+| Chat profiles | Row to open `UserProfilesView` (or `CreateProfile` when no users exist on Desktop) |
+| Use from desktop/mobile | Android: "Use from desktop" (`ConnectDesktopView`); Desktop: "Link a mobile" / "Linked mobiles" (`ConnectMobileView`) |
+| Settings | Row to open `SettingsView` with `ColorModeSwitcher` trailing |
+
+Platform behavior:
+- **Android**: `PlatformUserPicker` renders as bottom sheet with `AnimatedViewState` transitions; shows all users inline
+- **Desktop**: Sidebar panel; shows only active user in header, inactive users in separate section below divider
+
+### UserProfilesView
+
+Full profile management screen with search/password field:
+
+#### Search / Password Field
+
+Combined text field at the top (`searchTextOrPassword`):
+- In normal mode: Filters visible profiles by name
+- For hidden profiles: Acts as password entry to reveal hidden profiles
+- Trimmed search text compared against `user.anyNameContains()` and `correctPassword()`
+
+#### Profile List
+
+Each row rendered by `UserView` -> `UserProfilePickerItem`:
+
+| Element | Description |
+|---|---|
+| Active indicator | Checkmark icon (`ic_done_filled`) for the current active profile |
+| Profile image | 54dp avatar with `fontSizeSqrtMultiplier` scaling |
+| Display name | Profile's display name; bold for active, normal for inactive |
+| Unread count | Badge showing unread message count (`unreadCountStr`) with primary/secondary color based on mute state |
+| Muted indicator | `ic_notifications_off` icon when profile notifications are muted |
+| Hidden indicator | `ic_lock` icon for hidden profiles (only shown when revealed via password) |
+
+#### Profile Row Tap Action
+
+| Action | Description |
+|---|---|
+| Switch active | Tapping a profile row calls `changeActiveUser()` to activate the selected profile; all chats switch context |
+
+#### Profile Actions (Context Menu)
+
+Available via long-press / right-click on a profile row (`DefaultDropdownMenu`):
+
+| Action | Condition | Description |
+|---|---|---|
+| Mute | Visible, notifications on | `apiMuteUser()` mutes notifications; shows `showMuteProfileAlert` on first use |
+| Unmute | Visible, notifications off | `apiUnmuteUser()` restores notifications |
+| Hide | Visible, multiple visible users | Opens `HiddenProfileView` to set password |
+| Unhide | Hidden profile | `apiUnhideUser()` with password entry (`ProfileActionView` with `UserProfileAction.UNHIDE`) |
+| Delete | Any non-sole profile | Delete with confirmation dialog; options: "Delete with connections" (removes SMP queues) or "Delete data only" |
+
+#### Add Profile
+
+| Element | Description |
+|---|---|
+| Add button | "+" icon with "Add profile" text at bottom of list (hidden when searching) |
+| Auth required | Profile creation requires authentication via `withAuth` |
+| Create view | Opens `CreateProfile` in `ModalManager.center` |
+
+#### Profile Deletion (`removeUser`)
+
+Deletion flow:
+1. If hidden profile requiring password: opens `ProfileActionView` with `UserProfileAction.DELETE`
+2. If active profile: switches to another visible user first via `changeActiveUser_`, then deletes
+3. If last visible profile with hidden profiles: deletes user, then changes active to null; on Android, stops chat and resets to onboarding
+4. Cleans up wallpaper files and cancels notifications for the deleted user
+
+#### Hidden Profile Notice
+
+Shown once via `showHiddenProfilesNotice` preference:
+
+| Element | Description |
+|---|---|
+| Alert title | "Make profile private" |
+| Alert text | "You can hide or mute user profile" |
+| "Don't show again" | Disables the notice permanently |
+
+### Profile Password Validation
+
+| Function | Description |
+|---|---|
+| `correctPassword()` | Validates password against `user.viewPwdHash` using `chatPasswordHash(pwd, salt)` |
+| `passwordEntryRequired()` | Returns true if user is hidden, active, and password does not match current search text |
+| `userViewPassword()` | Extracts view password from search text for hidden user operations |
+
+## Source Files
+
+| File | Path |
+|---|---|
+| `UserProfilesView.kt` | `views/usersettings/UserProfilesView.kt` |
+| `UserPicker.kt` | `views/chatlist/UserPicker.kt` |
diff --git a/apps/multiplatform/spec/README.md b/apps/multiplatform/spec/README.md
new file mode 100644
index 0000000000..c5d9a3b4f7
--- /dev/null
+++ b/apps/multiplatform/spec/README.md
@@ -0,0 +1,137 @@
+# SimpleX Chat -- Kotlin Multiplatform Specification
+
+## Table of Contents
+
+1. [Executive Summary](#executive-summary)
+2. [Dependency Graph](#dependency-graph)
+3. [Specification Documents](#specification-documents)
+4. [Product Documents](#product-documents)
+5. [Source Entry Points](#source-entry-points)
+
+---
+
+## 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](architecture.md) | System layers, module structure, JNI bridge, app lifecycle, event streaming, platform abstraction |
+| State Management | [spec/state.md](state.md) | ChatModel singleton, ChatsContext, Chat data class, AppPreferences, ActiveChatState |
+| API | [spec/api.md](api.md) | ChatController command dispatch, ~150 API functions in 11 categories, CC/CR/API types |
+| Database | [spec/database.md](database.md) | SQLite database files, migrations, encryption, backup/restore |
+| Impact | [spec/impact.md](impact.md) | Source file → product concept mapping for change impact analysis |
+| Chat View | [spec/client/chat-view.md](client/chat-view.md) | ChatView, ChatItemView, message rendering, item interactions |
+| Chat List | [spec/client/chat-list.md](client/chat-list.md) | ChatListView, ChatPreviewView, filtering, search, tags |
+| Compose | [spec/client/compose.md](client/compose.md) | ComposeView, SendMsgView, ComposeState, attachments, mentions |
+| Navigation | [spec/client/navigation.md](client/navigation.md) | App screen routing, onboarding, settings, new chat flows |
+| Calls | [spec/services/calls.md](services/calls.md) | WebRTC call lifecycle, signaling, platform-specific call views |
+| Files | [spec/services/files.md](services/files.md) | File transfer (SMP inline / XFTP), CryptoFile encryption, platform file paths |
+| Notifications | [spec/services/notifications.md](services/notifications.md) | NtfManager, SimplexService, notification channels, background delivery |
+| Theme | [spec/services/theme.md](services/theme.md) | ThemeManager, color system, wallpapers, per-user overrides |
+
+---
+
+## Product Documents
+
+| Category | Path | Topic |
+|---|---|---|
+| Overview | [product/README.md](../product/README.md) | Product overview, capability map, navigation map |
+| Concepts | [product/concepts.md](../product/concepts.md) | 30 product concepts (PC1-PC30) mapped to docs + source |
+| Glossary | [product/glossary.md](../product/glossary.md) | Domain term definitions (9 sections) |
+| Rules | [product/rules.md](../product/rules.md) | 18 business rules in 6 categories |
+| Gaps | [product/gaps.md](../product/gaps.md) | 7 known gaps with recommendations |
+| Flows | [product/flows/](../product/flows/) | onboarding, messaging, connection, calling, file-transfer, group-lifecycle |
+| Views | [product/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`](../android/src/main/java/chat/simplex/app/SimplexApp.kt#L41) | `class SimplexApp` | 41 |
+| Android Activity | [`MainActivity.kt`](../android/src/main/java/chat/simplex/app/MainActivity.kt#L27) | `class MainActivity` | 27 |
+| Desktop Entry | [`Main.kt`](../desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt#L21) | `fun main()` | 21 |
+| Desktop App Window | [`DesktopApp.kt`](../common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt#L33) | `fun showApp()` | 33 |
+| Desktop Init | [`AppCommon.desktop.kt`](../common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt#L21) | `fun initApp()` | 21 |
+| Common App Screen | [`App.kt`](../common/src/commonMain/kotlin/chat/simplex/common/App.kt#L47) | `fun AppScreen()` | 47 |
+| JNI Bridge | [`Core.kt`](../common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt#L18) | `external fun initHS()` | 18 |
+| Chat Controller | [`SimpleXAPI.kt`](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L493) | `object ChatController` | 493 |
+| Chat Model | [`ChatModel.kt`](../common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt#L86) | `object ChatModel` | 86 |
+| App Preferences | [`SimpleXAPI.kt`](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L94) | `class AppPreferences` | 94 |
+| Platform Interface | [`Platform.kt`](../common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt#L15) | `interface PlatformInterface` | 15 |
+| Notification Manager | [`NtfManager.kt`](../common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt#L19) | `abstract class NtfManager` | 19 |
+| Theme Manager | [`ThemeManager.kt`](../common/src/commonMain/kotlin/chat/simplex/common/ui/theme/ThemeManager.kt#L18) | `object ThemeManager` | 18 |
+| Android Haskell Init | [`AppCommon.android.kt`](../common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt#L33) | `fun initHaskell(packageName: String)` | 33 |
+| Common Migrations | [`AppCommon.kt`](../common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt#L41) | `fun runMigrations()` | 41 |
+| Android Service | [`SimplexService.kt`](../android/src/main/java/chat/simplex/app/SimplexService.kt#L41) | `class SimplexService` | 41 |
+| Gradle Root | [`settings.gradle.kts`](../settings.gradle.kts#L22) | `include(":android", ":desktop", ":common")` | 22 |
+| Common Build | [`build.gradle.kts`](../common/build.gradle.kts#L14) | `kotlin { androidTarget(); jvm("desktop") }` | 14 |
diff --git a/apps/multiplatform/spec/api.md b/apps/multiplatform/spec/api.md
new file mode 100644
index 0000000000..15d5e141a0
--- /dev/null
+++ b/apps/multiplatform/spec/api.md
@@ -0,0 +1,435 @@
+# Chat API Reference
+
+## Table of Contents
+
+1. [Overview](#1-overview)
+2. [Command Categories](#2-command-categories)
+ - 2.1 [User Management](#21-user-management)
+ - 2.2 [Chat Lifecycle](#22-chat-lifecycle)
+ - 2.3 [Message Operations](#23-message-operations)
+ - 2.4 [Group Operations](#24-group-operations)
+ - 2.5 [Contact Operations](#25-contact-operations)
+ - 2.6 [File Operations](#26-file-operations)
+ - 2.7 [Call Operations](#27-call-operations)
+ - 2.8 [Settings & Network](#28-settings--network)
+ - 2.9 [Chat Tags](#29-chat-tags)
+ - 2.10 [Server Operators](#210-server-operators)
+ - 2.11 [Archive](#211-archive)
+3. [Response Types](#3-response-types)
+4. [Event Types](#4-event-types)
+5. [Error Types](#5-error-types)
+6. [Source Files](#6-source-files)
+
+---
+
+## 1. Overview
+
+The SimpleX Chat API bridge connects Kotlin/Compose UI code to the Haskell core via JNI. All communication follows a **command/response JSON protocol**:
+
+```
+Kotlin suspend fun api*()
+ -> ChatController.sendCmd(rhId, CC.*, ctrl)
+ -> serialize CC to cmdString (JSON)
+ -> chatSendCmdRetry(ctrl, cmdString, retryNum) [JNI / external fun]
+ -> Haskell core processes command
+ -> returns JSON response string
+ -> json.decodeFromString(responseString)
+ -> API.Result(rhId, CR.*) or API.Error(rhId, ChatError)
+ -> pattern-match on CR subclass -> update ChatModel / return data to UI
+```
+
+**Key types in the pipeline:**
+
+| Type | Role | Location |
+|------|------|----------|
+| `CC` (sealed class) | Command definitions (~165 subclasses) | [SimpleXAPI.kt#L3529](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L3529) |
+| `API` (sealed class) | Top-level response wrapper (`Result` / `Error`) | [SimpleXAPI.kt#L5975](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L5975) |
+| `CR` (sealed class) | Chat response variants (~180 subclasses) | [SimpleXAPI.kt#L6114](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L6114) |
+| `ChatError` (sealed class) | Error hierarchy | [SimpleXAPI.kt#L6974](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L6974) |
+| `ChatController` (object) | Singleton hosting all `api*` functions | [SimpleXAPI.kt#L493](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L493) |
+
+**JNI bridge functions** (declared in [Core.kt#L25](../common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt#L25)):
+
+```kotlin
+external fun chatMigrateInit(dbPath: String, dbKey: String, confirm: String): Array
+external fun chatCloseStore(ctrl: ChatCtrl): String
+external fun chatSendCmdRetry(ctrl: ChatCtrl, msg: String, retryNum: Int): String
+external fun chatSendRemoteCmdRetry(ctrl: ChatCtrl, rhId: Int, msg: String, retryNum: Int): String
+external fun chatRecvMsg(ctrl: ChatCtrl): String
+external fun chatRecvMsgWait(ctrl: ChatCtrl, timeout: Int): String
+```
+
+
+
+**`sendCmd` flow** ([SimpleXAPI.kt#L804](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L804)):
+
+1. Obtains the `ChatCtrl` handle (or uses the provided `otherCtrl`).
+2. Serializes the `CC` command to its `cmdString`.
+3. Dispatches to `Dispatchers.IO`; calls `chatSendCmdRetry` (local) or `chatSendRemoteCmdRetry` (remote host).
+4. Decodes the returned JSON string into `API`.
+5. Logs the result to the terminal item list.
+
+
+
+
+
+**Asynchronous event receiver** (`startReceiver`, [SimpleXAPI.kt#L660](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L660)):
+
+A long-running coroutine on `Dispatchers.IO` repeatedly calls `chatRecvMsgWait` (blocking JNI). Each received `API` message is dispatched to `processReceivedMsg` ([SimpleXAPI.kt#L2568](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2568)), which pattern-matches on `CR` subclasses to update `ChatModel` state and trigger notifications.
+
+---
+
+
+
+## 2. Command Categories
+
+All functions below are `suspend fun` members of `ChatController` ([SimpleXAPI.kt#L493](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L493)). The `rh` / `rhId` parameter is `Long?` identifying a remote host (`null` = local device).
+
+### 2.1 User Management
+
+| Command | Parameters | Description | Line |
+|---------|-----------|-------------|------|
+| `apiGetActiveUser` | `rh: Long?, ctrl: ChatCtrl?` | Fetch the currently active user profile | [L841](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L841) |
+| `apiCreateActiveUser` | `rh: Long?, p: Profile?, pastTimestamp: Boolean, ctrl: ChatCtrl?` | Create a new user profile and set it as active | [L851](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L851) |
+| `listUsers` | `rh: Long?` | List all user profiles sorted by display name | [L871](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L871) |
+| `apiSetActiveUser` | `rh: Long?, userId: Long, viewPwd: String?` | Switch the active user to a different profile | [L881](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L881) |
+| `apiSetAllContactReceipts` | `rh: Long?, enable: Boolean` | Enable/disable delivery receipts for all contacts globally | [L888](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L888) |
+| `apiSetUserContactReceipts` | `u: User, userMsgReceiptSettings: UserMsgReceiptSettings` | Set delivery receipt settings for user contacts | [L894](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L894) |
+| `apiSetUserGroupReceipts` | `u: User, userMsgReceiptSettings: UserMsgReceiptSettings` | Set delivery receipt settings for user groups | [L900](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L900) |
+| `apiSetUserAutoAcceptMemberContacts` | `u: User, enable: Boolean` | Toggle auto-accept for member contact requests | [L906](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L906) |
+| `apiHideUser` | `u: User, viewPwd: String` | Hide a user profile behind a password | [L912](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L912) |
+| `apiUnhideUser` | `u: User, viewPwd: String` | Unhide a previously hidden user profile | [L915](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L915) |
+| `apiMuteUser` | `u: User` | Mute all notifications for a user profile | [L918](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L918) |
+| `apiUnmuteUser` | `u: User` | Unmute notifications for a user profile | [L921](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L921) |
+| `apiDeleteUser` | `u: User, delSMPQueues: Boolean, viewPwd: String?` | Delete a user profile and optionally its SMP queues | [L930](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L930) |
+| `apiUpdateProfile` | `rh: Long?, profile: Profile` | Update the active user's display profile | [L1682](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1682) |
+| `apiSetProfileAddress` | `rh: Long?, on: Boolean` | Enable/disable including address in user profile | [L1694](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1694) |
+| `apiSetUserUIThemes` | `rh: Long?, userId: Long, themes: ThemeModeOverrides?` | Set UI theme overrides for a user | [L1732](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1732) |
+
+### 2.2 Chat Lifecycle
+
+| Command | Parameters | Description | Line |
+|---------|-----------|-------------|------|
+| `apiStartChat` | `ctrl: ChatCtrl?` | Start the chat engine (returns `true` if newly started) | [L937](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L937) |
+| `apiStopChat` | _(none)_ | Stop the chat engine | [L955](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L955) |
+| `apiSetAppFilePaths` | `filesFolder, tempFolder, assetsFolder, remoteHostsFolder: String, ctrl: ChatCtrl?` | Configure file-system paths for the Haskell core | [L961](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L961) |
+| `apiSetEncryptLocalFiles` | `enable: Boolean` | Enable/disable encryption of locally stored files | [L967](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L967) |
+| `apiSaveAppSettings` | `settings: AppSettings` | Persist application settings to the core | [L969](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L969) |
+| `apiGetAppSettings` | `settings: AppSettings` | Retrieve application settings from the core | [L975](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L975) |
+| `apiGetChats` | `rh: Long?` | Fetch the list of all chats for the active user | [L1013](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1013) |
+| `apiGetChat` | `rh, type, id, scope, contentTag, pagination, search` | Fetch a single chat with paginated messages | [L1031](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1031) |
+| `apiGetChatContentTypes` | `rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?` | Get available content type filters for a chat | [L1044](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1044) |
+| `apiClearChat` | `rh: Long?, type: ChatType, id: Long` | Delete all messages in a chat | [L1675](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1675) |
+| `apiDeleteChat` | `rh: Long?, type: ChatType, id: Long, chatDeleteMode: ChatDeleteMode` | Delete a chat (contact, group, connection, etc.) | [L1620](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1620) |
+| `apiChatRead` | `rh: Long?, type: ChatType, id: Long` | Mark a chat as read | [L1888](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1888) |
+| `apiChatItemsRead` | `rh, type, id, scope, itemIds` | Mark specific chat items as read | [L1902](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1902) |
+| `apiChatUnread` | `rh: Long?, type: ChatType, id: Long, unreadChat: Boolean` | Toggle a chat's unread flag | [L1909](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1909) |
+| `getChatItemTTL` | `rh: Long?` | Get the auto-delete TTL for chat items | [L1286](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1286) |
+| `setChatItemTTL` | `rh: Long?, chatItemTTL: ChatItemTTL` | Set the auto-delete TTL for chat items | [L1299](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1299) |
+| `setChatTTL` | `rh: Long?, chatType, id, chatItemTTL` | Set TTL for a specific chat | [L1306](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1306) |
+
+### 2.3 Message Operations
+
+| Command | Parameters | Description | Line |
+|---------|-----------|-------------|------|
+| `apiSendMessages` | `rh, type, id, scope, live, ttl, composedMessages` | Send one or more messages to a chat | [L1074](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1074) |
+| `apiCreateChatItems` | `rh: Long?, noteFolderId: Long, composedMessages: List` | Create items in a private notes folder | [L1111](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1111) |
+| `apiReportMessage` | `rh, groupId, chatItemId, reportReason, reportText` | Report a message in a group | [L1119](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1119) |
+| `apiGetChatItemInfo` | `rh, type, id, scope, itemId` | Get delivery info for a specific chat item | [L1126](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1126) |
+| `apiForwardChatItems` | `rh, toChatType, toChatId, toScope, fromChatType, fromChatId, fromScope, itemIds, ttl` | Forward messages between chats | [L1133](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1133) |
+| `apiPlanForwardChatItems` | `rh, fromChatType, fromChatId, fromScope, chatItemIds` | Check forward feasibility before forwarding | [L1138](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1138) |
+| `apiUpdateChatItem` | `rh, type, id, scope, itemId, updatedMessage, live` | Edit an existing message | [L1145](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1145) |
+| `apiChatItemReaction` | `rh, type, id, scope, itemId, add, reaction` | Add or remove a reaction to a message | [L1168](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1168) |
+| `apiGetReactionMembers` | `rh: Long?, groupId: Long, itemId: Long, reaction: MsgReaction` | List members who reacted with a specific emoji | [L1175](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1175) |
+| `apiDeleteChatItems` | `rh, type, id, scope, itemIds, mode` | Delete messages (for self or for everyone) | [L1183](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1183) |
+| `apiDeleteMemberChatItems` | `rh: Long?, groupId: Long, itemIds: List` | Moderate: delete another member's messages | [L1190](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1190) |
+| `apiArchiveReceivedReports` | `rh: Long?, groupId: Long` | Archive all received reports in a group | [L1197](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1197) |
+| `apiDeleteReceivedReports` | `rh: Long?, groupId: Long, itemIds: List, mode: CIDeleteMode` | Delete specific received reports | [L1204](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1204) |
+
+### 2.4 Group Operations
+
+| Command | Parameters | Description | Line |
+|---------|-----------|-------------|------|
+| `apiNewGroup` | `rh: Long?, incognito: Boolean, groupProfile: GroupProfile` | Create a new group | [L2092](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2092) |
+| `apiAddMember` | `rh: Long?, groupId: Long, contactId: Long, memberRole: GroupMemberRole` | Invite a contact to a group | [L2100](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2100) |
+| `apiJoinGroup` | `rh: Long?, groupId: Long` | Accept a group invitation | [L2109](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2109) |
+| `apiAcceptMember` | `rh: Long?, groupId: Long, groupMemberId: Long, memberRole: GroupMemberRole` | Accept a member joining via group link | [L2135](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2135) |
+| `apiDeleteMemberSupportChat` | `rh: Long?, groupId: Long, groupMemberId: Long` | Delete a member's support chat | [L2144](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2144) |
+| `apiRemoveMembers` | `rh: Long?, groupId: Long, memberIds: List, withMessages: Boolean` | Remove members from a group | [L2151](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2151) |
+| `apiMembersRole` | `rh: Long?, groupId: Long, memberIds: List, memberRole: GroupMemberRole` | Change the role of group members | [L2160](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2160) |
+| `apiBlockMembersForAll` | `rh: Long?, groupId: Long, memberIds: List, blocked: Boolean` | Block/unblock members for all group participants | [L2169](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2169) |
+| `apiLeaveGroup` | `rh: Long?, groupId: Long` | Leave a group | [L2178](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2178) |
+| `apiListMembers` | `rh: Long?, groupId: Long` | List all members of a group | [L2185](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2185) |
+| `apiUpdateGroup` | `rh: Long?, groupId: Long, groupProfile: GroupProfile` | Update group profile (name, image, etc.) | [L2192](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2192) |
+| `apiCreateGroupLink` | `rh: Long?, groupId: Long, memberRole: GroupMemberRole` | Create a group invitation link | [L2211](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2211) |
+| `apiGroupLinkMemberRole` | `rh: Long?, groupId: Long, memberRole: GroupMemberRole` | Update the default role for group link joins | [L2226](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2226) |
+| `apiDeleteGroupLink` | `rh: Long?, groupId: Long` | Delete the group invitation link | [L2235](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2235) |
+| `apiGetGroupLink` | `rh: Long?, groupId: Long` | Retrieve the current group invitation link | [L2245](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2245) |
+| `apiAddGroupShortLink` | `rh: Long?, groupId: Long` | Create a short link for the group | [L2252](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2252) |
+| `apiCreateMemberContact` | `rh: Long?, groupId: Long, groupMemberId: Long` | Create a direct contact from a group member | [L2262](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2262) |
+| `apiSendMemberContactInvitation` | `rh: Long?, contactId: Long, mc: MsgContent` | Send a direct message invitation to a group member | [L2271](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2271) |
+| `apiAcceptMemberContact` | `rh: Long?, contactId: Long` | Accept a member's direct contact invitation | [L2280](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2280) |
+| `apiSetMemberSettings` | `rh: Long?, groupId: Long, groupMemberId: Long, memberSettings: GroupMemberSettings` | Configure per-member settings (e.g., mentions) | [L1343](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1343) |
+| `apiGroupMemberInfo` | `rh: Long?, groupId: Long, groupMemberId: Long` | Get a group member's info and connection stats | [L1353](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1353) |
+| `apiSetGroupAlias` | `rh: Long?, groupId: Long, localAlias: String` | Set a local alias for a group | [L1718](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1718) |
+
+### 2.5 Contact Operations
+
+| Command | Parameters | Description | Line |
+|---------|-----------|-------------|------|
+| `apiAddContact` | `rh: Long?, incognito: Boolean` | Create a one-time invitation link for a new contact | [L1444](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1444) |
+| `apiSetConnectionIncognito` | `rh: Long?, connId: Long, incognito: Boolean` | Toggle incognito on a pending connection | [L1455](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1455) |
+| `apiChangeConnectionUser` | `rh: Long?, connId: Long, userId: Long` | Change the user profile on a pending connection | [L1464](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1464) |
+| `apiConnectPlan` | `rh: Long?, connLink: String, inProgress: MutableState` | Analyze a connection link before connecting | [L1474](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1474) |
+| `apiConnect` | `rh: Long?, incognito: Boolean, connLink: CreatedConnLink` | Connect via an invitation or address link | [L1482](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1482) |
+| `apiPrepareContact` | `rh, connLink, contactShortLinkData` | Prepare a contact chat from a short link (before connecting) | [L1546](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1546) |
+| `apiPrepareGroup` | `rh, connLink, groupShortLinkData` | Prepare a group chat from a short link (before connecting) | [L1555](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1555) |
+| `apiConnectPreparedContact` | `rh, contactId, incognito, msg` | Connect to a previously prepared contact | [L1580](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1580) |
+| `apiConnectPreparedGroup` | `rh, groupId, incognito, msg` | Join a previously prepared group | [L1590](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1590) |
+| `apiConnectContactViaAddress` | `rh: Long?, incognito: Boolean, contactId: Long` | Connect to a contact using their public address | [L1600](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1600) |
+| `apiDeleteContact` | `rh: Long?, id: Long, chatDeleteMode: ChatDeleteMode` | Delete a contact and return the deleted Contact | [L1644](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1644) |
+| `apiContactInfo` | `rh: Long?, contactId: Long` | Get a contact's connection stats and custom profile | [L1346](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1346) |
+| `apiSetContactAlias` | `rh: Long?, contactId: Long, localAlias: String` | Set a local display alias for a contact | [L1711](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1711) |
+| `apiSetConnectionAlias` | `rh: Long?, connId: Long, localAlias: String` | Set a local display alias for a pending connection | [L1725](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1725) |
+| `apiSetContactPrefs` | `rh: Long?, contactId: Long, prefs: ChatPreferences` | Update feature preferences for a contact | [L1704](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1704) |
+| `apiCreateUserAddress` | `rh: Long?` | Create a long-term public contact address | [L1746](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1746) |
+| `apiDeleteUserAddress` | `rh: Long?` | Delete the user's public contact address | [L1762](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1762) |
+| `apiAddMyAddressShortLink` | `rh: Long?` | Create a short link for the user's address | [L1784](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1784) |
+| `apiSetUserAddressSettings` | `rh: Long?, settings: AddressSettings` | Configure auto-accept for incoming contact requests | [L1795](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1795) |
+| `apiAcceptContactRequest` | `rh: Long?, incognito: Boolean, contactReqId: Long` | Accept an incoming contact request | [L1809](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1809) |
+| `apiRejectContactRequest` | `rh: Long?, contactReqId: Long` | Reject an incoming contact request | [L1832](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1832) |
+| `apiSwitchContact` | `rh: Long?, contactId: Long` | Initiate SMP server switch for a contact | [L1374](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1374) |
+| `apiAbortSwitchContact` | `rh: Long?, contactId: Long` | Abort an in-progress server switch | [L1388](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1388) |
+| `apiSyncContactRatchet` | `rh: Long?, contactId: Long, force: Boolean` | Force ratchet synchronization with a contact | [L1402](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1402) |
+| `apiGetContactCode` | `rh: Long?, contactId: Long` | Get the security verification code for a contact | [L1416](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1416) |
+| `apiVerifyContact` | `rh: Long?, contactId: Long, connectionCode: String?` | Verify a contact's security code | [L1430](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1430) |
+
+### 2.6 File Operations
+
+| Command | Parameters | Description | Line |
+|---------|-----------|-------------|------|
+| `receiveFiles` | `rhId, user, fileIds, userApprovedRelays, auto` | Accept and download one or more files (handles relay approval) | [L1946](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1946) |
+| `receiveFile` | `rhId, user, fileId, userApprovedRelays, auto` | Accept and download a single file (convenience wrapper) | [L2062](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2062) |
+| `cancelFile` | `rh: Long?, user: User, fileId: Long` | Cancel an in-progress file transfer and clean up | [L2072](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2072) |
+| `apiCancelFile` | `rh: Long?, fileId: Long, ctrl: ChatCtrl?` | Cancel a file transfer (low-level, returns updated chat item) | [L2080](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2080) |
+| `uploadStandaloneFile` | `user: UserLike, file: CryptoFile, ctrl: ChatCtrl?` | Upload a standalone file (used for migration) | [L1916](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1916) |
+| `downloadStandaloneFile` | `user: UserLike, url: String, file: CryptoFile, ctrl: ChatCtrl?` | Download a standalone file by URL | [L1926](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1926) |
+| `standaloneFileInfo` | `url: String, ctrl: ChatCtrl?` | Retrieve metadata for a standalone file link | [L1936](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1936) |
+
+### 2.7 Call Operations
+
+| Command | Parameters | Description | Line |
+|---------|-----------|-------------|------|
+| `apiGetCallInvitations` | `rh: Long?` | Retrieve pending call invitations | [L1842](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1842) |
+| `apiSendCallInvitation` | `rh: Long?, contact: Contact, callType: CallType` | Initiate a call by sending an invitation | [L1849](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1849) |
+| `apiRejectCall` | `rh: Long?, contact: Contact` | Reject an incoming call | [L1854](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1854) |
+| `apiSendCallOffer` | `rh, contact, rtcSession, rtcIceCandidates, media, capabilities` | Send a WebRTC call offer | [L1859](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1859) |
+| `apiSendCallAnswer` | `rh: Long?, contact: Contact, rtcSession: String, rtcIceCandidates: String` | Send a WebRTC call answer | [L1866](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1866) |
+| `apiSendCallExtraInfo` | `rh: Long?, contact: Contact, rtcIceCandidates: String` | Send additional ICE candidates during a call | [L1872](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1872) |
+| `apiEndCall` | `rh: Long?, contact: Contact` | End an active call | [L1878](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1878) |
+| `apiCallStatus` | `rh: Long?, contact: Contact, status: WebRTCCallStatus` | Report call status updates to the core | [L1883](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1883) |
+
+### 2.8 Settings & Network
+
+| Command | Parameters | Description | Line |
+|---------|-----------|-------------|------|
+| `apiSetNetworkConfig` | `cfg: NetCfg, showAlertOnError: Boolean, ctrl: ChatCtrl?` | Apply network configuration (SOCKS proxy, timeouts, etc.) | [L1313](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1313) |
+| `apiSetNetworkInfo` | `networkInfo: UserNetworkInfo` | Update network reachability information | [L1340](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1340) |
+| `apiSetSettings` | `rh: Long?, type: ChatType, id: Long, settings: ChatSettings` | Update per-chat settings (notifications, favorites) | [L1333](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1333) |
+| `apiStorageEncryption` | `currentKey: String, newKey: String` | Change the database encryption passphrase | [L999](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L999) |
+| `testStorageEncryption` | `key: String, ctrl: ChatCtrl?` | Verify a database encryption key is correct | [L1006](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1006) |
+| `testProtoServer` | `rh: Long?, server: String` | Test connectivity to a protocol server | [L1211](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1211) |
+| `reconnectServer` | `rh: Long?, server: String` | Reconnect to a specific server | [L1326](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1326) |
+| `reconnectAllServers` | `rh: Long?` | Reconnect to all servers | [L1331](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1331) |
+| `apiSetChatUIThemes` | `rh: Long?, chatId: ChatId, themes: ThemeModeOverrides?` | Set per-chat UI theme overrides | [L1739](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1739) |
+| `apiContactQueueInfo` | `rh: Long?, contactId: Long` | Get server queue diagnostics for a contact | [L1360](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1360) |
+| `apiGroupMemberQueueInfo` | `rh: Long?, groupId: Long, groupMemberId: Long` | Get server queue diagnostics for a group member | [L1367](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1367) |
+
+### 2.9 Chat Tags
+
+| Command | Parameters | Description | Line |
+|---------|-----------|-------------|------|
+| `apiCreateChatTag` | `rh: Long?, tag: ChatTagData` | Create a new chat tag (folder/label) | [L1052](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1052) |
+| `apiSetChatTags` | `rh: Long?, type: ChatType, id: Long, tagIds: List` | Assign tags to a chat | [L1060](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1060) |
+| `apiDeleteChatTag` | `rh: Long?, tagId: Long` | Delete a chat tag | [L1068](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1068) |
+| `apiUpdateChatTag` | `rh: Long?, tagId: Long, tag: ChatTagData` | Update a chat tag's name or emoji | [L1070](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1070) |
+| `apiReorderChatTags` | `rh: Long?, tagIds: List` | Set the display order of chat tags | [L1072](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1072) |
+
+### 2.10 Server Operators
+
+| Command | Parameters | Description | Line |
+|---------|-----------|-------------|------|
+| `getServerOperators` | `rh: Long?` | Get server operator conditions detail | [L1219](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1219) |
+| `setServerOperators` | `rh: Long?, operators: List` | Update the list of server operators | [L1226](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1226) |
+| `getUserServers` | `rh: Long?` | Get the user's configured servers per operator | [L1233](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1233) |
+| `setUserServers` | `rh: Long?, userServers: List` | Save user's configured servers per operator | [L1241](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1241) |
+| `validateServers` | `rh: Long?, userServers: List` | Validate server configuration for errors | [L1253](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1253) |
+| `getUsageConditions` | `rh: Long?` | Get current and accepted usage conditions | [L1261](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1261) |
+| `setConditionsNotified` | `rh: Long?, conditionsId: Long` | Mark conditions as shown to user | [L1268](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1268) |
+| `acceptConditions` | `rh: Long?, conditionsId: Long, operatorIds: List` | Accept usage conditions for operators | [L1275](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1275) |
+
+### 2.11 Archive
+
+| Command | Parameters | Description | Line |
+|---------|-----------|-------------|------|
+| `apiExportArchive` | `config: ArchiveConfig` | Export chat database to a ZIP archive | [L981](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L981) |
+| `apiImportArchive` | `config: ArchiveConfig` | Import chat database from a ZIP archive | [L987](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L987) |
+| `apiDeleteStorage` | _(none)_ | Delete all chat database storage | [L993](../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L993) |
+
+