mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-04-27 15:06:12 +00:00
Merge branch 'master' into ep/smp-server-pages
This commit is contained in:
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
|
||||
[<img src="./images/trail-of-bits.jpg" height="80">](http://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html) [<img src="./images/privacy-guides.jpg" height="64">](https://www.privacyguides.org/en/real-time-communication/#simplex-chat) [<img src="./images/whonix-logo.jpg" height="64">](https://www.whonix.org/wiki/Chat#Recommendation) [<img src="./images/kuketz-blog.jpg" height="64">](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.
|
||||
|
||||
|
||||
@@ -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 |
|
||||
@@ -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<R>()` - Send typed commands and parse responses
|
||||
- `recvSimpleXMsg<R>()` - 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`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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<T>(bgDelay: Double? = nil, f: @escaping () -> T) -> T {
|
||||
return r
|
||||
}
|
||||
|
||||
// Spec: spec/api.md#chatSendCmdSync
|
||||
@inline(__always)
|
||||
func chatSendCmdSync<R: ChatAPIResult>(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, ctrl: chat_ctrl? = nil, log: Bool = true) throws -> R {
|
||||
let res: APIResult<R> = chatApiSendCmdSync(cmd, bgTask: bgTask, bgDelay: bgDelay, ctrl: ctrl, log: log)
|
||||
return try apiResult(res)
|
||||
}
|
||||
|
||||
// Spec: spec/api.md#chatApiSendCmdSync
|
||||
func chatApiSendCmdSync<R: ChatAPIResult>(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, ctrl: chat_ctrl? = nil, retryNum: Int32 = 0, log: Bool = true) -> APIResult<R> {
|
||||
if log {
|
||||
logger.debug("chatSendCmd \(cmd.cmdType)")
|
||||
@@ -112,12 +116,14 @@ func chatApiSendCmdSync<R: ChatAPIResult>(_ cmd: ChatCommand, bgTask: Bool = tru
|
||||
return resp
|
||||
}
|
||||
|
||||
// Spec: spec/api.md#chatSendCmd
|
||||
@inline(__always)
|
||||
func chatSendCmd<R: ChatAPIResult>(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, ctrl: chat_ctrl? = nil, log: Bool = true) async throws -> R {
|
||||
let res: APIResult<R> = await chatApiSendCmd(cmd, bgTask: bgTask, bgDelay: bgDelay, ctrl: ctrl, log: log)
|
||||
return try apiResult(res)
|
||||
}
|
||||
|
||||
// Spec: spec/api.md#chatApiSendCmdWithRetry
|
||||
func chatApiSendCmdWithRetry<R: ChatAPIResult>(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, inProgress: BoxedValue<Bool>? = nil, retryNum: Int32 = 0) async -> APIResult<R>? {
|
||||
let r: APIResult<R> = 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<R: ChatAPIResult>(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, ctrl: chat_ctrl? = nil, retryNum: Int32 = 0, log: Bool = true) async -> APIResult<R> {
|
||||
await withCheckedContinuation { cont in
|
||||
@@ -226,6 +233,7 @@ func apiResult<R: ChatAPIResult>(_ res: APIResult<R>) throws -> R {
|
||||
}
|
||||
}
|
||||
|
||||
// Spec: spec/api.md#chatRecvMsg
|
||||
func chatRecvMsg(_ ctrl: chat_ctrl? = nil) async -> APIResult<ChatEvent>? {
|
||||
await withCheckedContinuation { cont in
|
||||
_ = withBGTask(bgDelay: msgDelay) { () -> APIResult<ChatEvent>? 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<Void, Never>?
|
||||
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)")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -38,6 +38,7 @@ extension EnvironmentValues {
|
||||
}
|
||||
}
|
||||
|
||||
// Spec: spec/client/chat-view.md#ChatItemView
|
||||
struct ChatItemView: View {
|
||||
@ObservedObject var chat: Chat
|
||||
@ObservedObject var im: ItemsModel
|
||||
|
||||
@@ -15,6 +15,7 @@ func apiLoadMessages(
|
||||
_ chatId: ChatId,
|
||||
_ im: ItemsModel,
|
||||
_ pagination: ChatPagination,
|
||||
_ contentTag: MsgContentTag? = nil,
|
||||
_ search: String = "",
|
||||
_ openAroundItemId: ChatItem.ID? = nil,
|
||||
_ visibleItemIndexesNonReversed: @MainActor () -> ClosedRange<Int> = { 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
|
||||
|
||||
@@ -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<ChatItem>) -> Array<ChatItem> {
|
||||
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<MsgContentTag> = [.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()
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// Created by Evgeny on 10/04/2023.
|
||||
// Copyright © 2023 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
// Spec: spec/architecture.md
|
||||
|
||||
import SwiftUI
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// Created by Evgeny on 11/04/2023.
|
||||
// Copyright © 2023 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
// Spec: spec/architecture.md
|
||||
|
||||
import SwiftUI
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<T: Decodable>(_ string: String) -> T? {
|
||||
do {
|
||||
return try YAMLDecoder().decode(T.self, from: string)
|
||||
@@ -1150,6 +1156,7 @@ private func decodeYAML<T: Decodable>(_ 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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -792,6 +792,10 @@ swipe action</note>
|
||||
<target>Всички членове на групата ще останат свързани.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages" xml:space="preserve">
|
||||
<source>All messages</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." xml:space="preserve">
|
||||
<source>All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.</source>
|
||||
<target>Всички съобщения и файлове се изпращат с **криптиране от край до край**, с постквантова сигурност в директните съобщения.</target>
|
||||
@@ -1142,6 +1146,10 @@ swipe action</note>
|
||||
<target>Аудио и видео разговори</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Audio call" xml:space="preserve">
|
||||
<source>Audio call</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Audio/video calls" xml:space="preserve">
|
||||
<source>Audio/video calls</source>
|
||||
<target>Аудио/видео разговори</target>
|
||||
@@ -2552,6 +2560,14 @@ swipe action</note>
|
||||
<target>Изтрий съобщението на члена?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete member messages" xml:space="preserve">
|
||||
<source>Delete member messages</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete member messages?" xml:space="preserve">
|
||||
<source>Delete member messages?</source>
|
||||
<note>alert title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete message?" xml:space="preserve">
|
||||
<source>Delete message?</source>
|
||||
<target>Изтрий съобщението?</target>
|
||||
@@ -2560,7 +2576,8 @@ swipe action</note>
|
||||
<trans-unit id="Delete messages" xml:space="preserve">
|
||||
<source>Delete messages</source>
|
||||
<target>Изтрий съобщенията</target>
|
||||
<note>alert button</note>
|
||||
<note>alert action
|
||||
alert button</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete messages after" xml:space="preserve">
|
||||
<source>Delete messages after</source>
|
||||
@@ -3741,6 +3758,10 @@ snd error text</note>
|
||||
<target>Файловете и медията са забранени!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Filter" xml:space="preserve">
|
||||
<source>Filter</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Filter unread and favorite chats." xml:space="preserve">
|
||||
<source>Filter unread and favorite chats.</source>
|
||||
<target>Филтрирайте непрочетените и любимите чатове.</target>
|
||||
@@ -4194,6 +4215,10 @@ Error: %2$@</source>
|
||||
<target>Изображението ще бъде получено, когато вашият контакт е онлайн, моля, изчакайте или проверете по-късно!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Images" xml:space="preserve">
|
||||
<source>Images</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Immediately" xml:space="preserve">
|
||||
<source>Immediately</source>
|
||||
<target>Веднага</target>
|
||||
@@ -4442,6 +4467,10 @@ More improvements are coming soon!</source>
|
||||
<target>Покани приятели</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invite member" xml:space="preserve">
|
||||
<source>Invite member</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invite members" xml:space="preserve">
|
||||
<source>Invite members</source>
|
||||
<target>Покани членове</target>
|
||||
@@ -4658,6 +4687,10 @@ This is your link for group %@!</source>
|
||||
<target>Запомнени настолни устройства</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Links" xml:space="preserve">
|
||||
<source>Links</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="List" xml:space="preserve">
|
||||
<source>List</source>
|
||||
<note>swipe action</note>
|
||||
@@ -4773,6 +4806,10 @@ This is your link for group %@!</source>
|
||||
<source>Member is deleted - can't accept request</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member messages will be deleted - this cannot be undone!" xml:space="preserve">
|
||||
<source>Member messages will be deleted - this cannot be undone!</source>
|
||||
<note>alert message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member reports" xml:space="preserve">
|
||||
<source>Member reports</source>
|
||||
<note>chat feature</note>
|
||||
@@ -4793,12 +4830,12 @@ This is your link for group %@!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member will be removed from chat - this cannot be undone!" xml:space="preserve">
|
||||
<source>Member will be removed from chat - this cannot be undone!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member will be removed from group - this cannot be undone!" xml:space="preserve">
|
||||
<source>Member will be removed from group - this cannot be undone!</source>
|
||||
<target>Членът ще бъде премахнат от групата - това не може да бъде отменено!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member will join the group, accept member?" xml:space="preserve">
|
||||
<source>Member will join the group, accept member?</source>
|
||||
@@ -6296,7 +6333,11 @@ swipe action</note>
|
||||
<trans-unit id="Remove" xml:space="preserve">
|
||||
<source>Remove</source>
|
||||
<target>Премахване</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove and delete messages" xml:space="preserve">
|
||||
<source>Remove and delete messages</source>
|
||||
<note>alert action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove archive?" xml:space="preserve">
|
||||
<source>Remove archive?</source>
|
||||
@@ -6318,7 +6359,7 @@ swipe action</note>
|
||||
<trans-unit id="Remove member?" xml:space="preserve">
|
||||
<source>Remove member?</source>
|
||||
<target>Острани член?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove passphrase from keychain?" xml:space="preserve">
|
||||
<source>Remove passphrase from keychain?</source>
|
||||
@@ -6703,11 +6744,31 @@ chat item action</note>
|
||||
<target>Лентата за търсене приема линк за връзка.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search files" xml:space="preserve">
|
||||
<source>Search files</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search images" xml:space="preserve">
|
||||
<source>Search images</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search links" xml:space="preserve">
|
||||
<source>Search links</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search or paste SimpleX link" xml:space="preserve">
|
||||
<source>Search or paste SimpleX link</source>
|
||||
<target>Търсене или поставяне на SimpleX линк</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search videos" xml:space="preserve">
|
||||
<source>Search videos</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search voice messages" xml:space="preserve">
|
||||
<source>Search voice messages</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Secondary" xml:space="preserve">
|
||||
<source>Secondary</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -8432,6 +8493,10 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>Видеото ще бъде получено, когато вашият контакт е онлайн, моля, изчакайте или проверете по-късно!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Videos" xml:space="preserve">
|
||||
<source>Videos</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Videos and files up to 1gb" xml:space="preserve">
|
||||
<source>Videos and files up to 1gb</source>
|
||||
<target>Видео и файлове до 1gb</target>
|
||||
|
||||
@@ -782,6 +782,10 @@ swipe action</note>
|
||||
<target>Všichni členové skupiny zůstanou připojeni.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages" xml:space="preserve">
|
||||
<source>All messages</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." xml:space="preserve">
|
||||
<source>All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -1117,6 +1121,10 @@ swipe action</note>
|
||||
<target>Hlasové a video hovory</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Audio call" xml:space="preserve">
|
||||
<source>Audio call</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Audio/video calls" xml:space="preserve">
|
||||
<source>Audio/video calls</source>
|
||||
<target>Audio/video hovory</target>
|
||||
@@ -2438,6 +2446,14 @@ swipe action</note>
|
||||
<target>Smazat zprávu člena?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete member messages" xml:space="preserve">
|
||||
<source>Delete member messages</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete member messages?" xml:space="preserve">
|
||||
<source>Delete member messages?</source>
|
||||
<note>alert title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete message?" xml:space="preserve">
|
||||
<source>Delete message?</source>
|
||||
<target>Smazat zprávu?</target>
|
||||
@@ -2446,7 +2462,8 @@ swipe action</note>
|
||||
<trans-unit id="Delete messages" xml:space="preserve">
|
||||
<source>Delete messages</source>
|
||||
<target>Smazat zprávy</target>
|
||||
<note>alert button</note>
|
||||
<note>alert action
|
||||
alert button</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete messages after" xml:space="preserve">
|
||||
<source>Delete messages after</source>
|
||||
@@ -3596,6 +3613,10 @@ snd error text</note>
|
||||
<target>Soubory a média jsou zakázány!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Filter" xml:space="preserve">
|
||||
<source>Filter</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Filter unread and favorite chats." xml:space="preserve">
|
||||
<source>Filter unread and favorite chats.</source>
|
||||
<target>Filtrovat nepřečtené a oblíbené chaty.</target>
|
||||
@@ -4037,6 +4058,10 @@ Error: %2$@</source>
|
||||
<target>Obrázek bude přijat, až bude váš kontakt online, vyčkejte prosím nebo se podívejte později!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Images" xml:space="preserve">
|
||||
<source>Images</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Immediately" xml:space="preserve">
|
||||
<source>Immediately</source>
|
||||
<target>Ihned</target>
|
||||
@@ -4272,6 +4297,10 @@ More improvements are coming soon!</source>
|
||||
<target>Pozvat přátele</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invite member" xml:space="preserve">
|
||||
<source>Invite member</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invite members" xml:space="preserve">
|
||||
<source>Invite members</source>
|
||||
<target>Pozvat členy</target>
|
||||
@@ -4479,6 +4508,10 @@ This is your link for group %@!</source>
|
||||
<source>Linked desktops</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Links" xml:space="preserve">
|
||||
<source>Links</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="List" xml:space="preserve">
|
||||
<source>List</source>
|
||||
<note>swipe action</note>
|
||||
@@ -4594,6 +4627,10 @@ This is your link for group %@!</source>
|
||||
<source>Member is deleted - can't accept request</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member messages will be deleted - this cannot be undone!" xml:space="preserve">
|
||||
<source>Member messages will be deleted - this cannot be undone!</source>
|
||||
<note>alert message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member reports" xml:space="preserve">
|
||||
<source>Member reports</source>
|
||||
<note>chat feature</note>
|
||||
@@ -4614,12 +4651,12 @@ This is your link for group %@!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member will be removed from chat - this cannot be undone!" xml:space="preserve">
|
||||
<source>Member will be removed from chat - this cannot be undone!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member will be removed from group - this cannot be undone!" xml:space="preserve">
|
||||
<source>Member will be removed from group - this cannot be undone!</source>
|
||||
<target>Člen bude odstraněn ze skupiny - toto nelze vzít zpět!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member will join the group, accept member?" xml:space="preserve">
|
||||
<source>Member will join the group, accept member?</source>
|
||||
@@ -6074,7 +6111,11 @@ swipe action</note>
|
||||
<trans-unit id="Remove" xml:space="preserve">
|
||||
<source>Remove</source>
|
||||
<target>Odstranit</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove and delete messages" xml:space="preserve">
|
||||
<source>Remove and delete messages</source>
|
||||
<note>alert action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove archive?" xml:space="preserve">
|
||||
<source>Remove archive?</source>
|
||||
@@ -6096,7 +6137,7 @@ swipe action</note>
|
||||
<trans-unit id="Remove member?" xml:space="preserve">
|
||||
<source>Remove member?</source>
|
||||
<target>Odebrat člena?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove passphrase from keychain?" xml:space="preserve">
|
||||
<source>Remove passphrase from keychain?</source>
|
||||
@@ -6471,10 +6512,30 @@ chat item action</note>
|
||||
<source>Search bar accepts invitation links.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search files" xml:space="preserve">
|
||||
<source>Search files</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search images" xml:space="preserve">
|
||||
<source>Search images</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search links" xml:space="preserve">
|
||||
<source>Search links</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search or paste SimpleX link" xml:space="preserve">
|
||||
<source>Search or paste SimpleX link</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search videos" xml:space="preserve">
|
||||
<source>Search videos</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search voice messages" xml:space="preserve">
|
||||
<source>Search voice messages</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Secondary" xml:space="preserve">
|
||||
<source>Secondary</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -8156,6 +8217,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
|
||||
<target>Video obdržíte, až bude váš kontakt online, vyčkejte prosím nebo zkontrolujte později!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Videos" xml:space="preserve">
|
||||
<source>Videos</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Videos and files up to 1gb" xml:space="preserve">
|
||||
<source>Videos and files up to 1gb</source>
|
||||
<target>Videa a soubory až do velikosti 1 gb</target>
|
||||
|
||||
@@ -792,6 +792,10 @@ swipe action</note>
|
||||
<target>Alle Gruppenmitglieder bleiben verbunden.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages" xml:space="preserve">
|
||||
<source>All messages</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." xml:space="preserve">
|
||||
<source>All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.</source>
|
||||
<target>Alle Nachrichten und Dateien werden **Ende-zu-Ende verschlüsselt** versendet - in Direkt-Nachrichten mit Post-Quantum-Security.</target>
|
||||
@@ -1142,6 +1146,10 @@ swipe action</note>
|
||||
<target>Audio- und Videoanrufe</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Audio call" xml:space="preserve">
|
||||
<source>Audio call</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Audio/video calls" xml:space="preserve">
|
||||
<source>Audio/video calls</source>
|
||||
<target>Audio-/Video-Anrufe</target>
|
||||
@@ -2579,6 +2587,14 @@ swipe action</note>
|
||||
<target>Nachricht des Mitglieds löschen?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete member messages" xml:space="preserve">
|
||||
<source>Delete member messages</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete member messages?" xml:space="preserve">
|
||||
<source>Delete member messages?</source>
|
||||
<note>alert title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete message?" xml:space="preserve">
|
||||
<source>Delete message?</source>
|
||||
<target>Die Nachricht löschen?</target>
|
||||
@@ -2587,7 +2603,8 @@ swipe action</note>
|
||||
<trans-unit id="Delete messages" xml:space="preserve">
|
||||
<source>Delete messages</source>
|
||||
<target>Nachrichten löschen</target>
|
||||
<note>alert button</note>
|
||||
<note>alert action
|
||||
alert button</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete messages after" xml:space="preserve">
|
||||
<source>Delete messages after</source>
|
||||
@@ -3851,6 +3868,10 @@ snd error text</note>
|
||||
<target>Dateien und Medien sind nicht erlaubt!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Filter" xml:space="preserve">
|
||||
<source>Filter</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Filter unread and favorite chats." xml:space="preserve">
|
||||
<source>Filter unread and favorite chats.</source>
|
||||
<target>Nach ungelesenen und favorisierten Chats filtern.</target>
|
||||
@@ -4335,6 +4356,10 @@ Fehler: %2$@</target>
|
||||
<target>Das Bild wird heruntergeladen, sobald Ihr Kontakt online ist. Bitte warten oder schauen Sie später nochmal nach!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Images" xml:space="preserve">
|
||||
<source>Images</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Immediately" xml:space="preserve">
|
||||
<source>Immediately</source>
|
||||
<target>Sofort</target>
|
||||
@@ -4594,6 +4619,10 @@ Weitere Verbesserungen sind bald verfügbar!</target>
|
||||
<target>Freunde einladen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invite member" xml:space="preserve">
|
||||
<source>Invite member</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invite members" xml:space="preserve">
|
||||
<source>Invite members</source>
|
||||
<target>Mitglieder einladen</target>
|
||||
@@ -4817,6 +4846,10 @@ Das ist Ihr Link für die Gruppe %@!</target>
|
||||
<target>Verknüpfte Desktops</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Links" xml:space="preserve">
|
||||
<source>Links</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="List" xml:space="preserve">
|
||||
<source>List</source>
|
||||
<target>Liste</target>
|
||||
@@ -4942,6 +4975,10 @@ Das ist Ihr Link für die Gruppe %@!</target>
|
||||
<target>Mitglied ist gelöscht - Anfrage kann nicht angenommen werden</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member messages will be deleted - this cannot be undone!" xml:space="preserve">
|
||||
<source>Member messages will be deleted - this cannot be undone!</source>
|
||||
<note>alert message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member reports" xml:space="preserve">
|
||||
<source>Member reports</source>
|
||||
<target>Mitglieder-Meldungen</target>
|
||||
@@ -4965,12 +5002,12 @@ Das ist Ihr Link für die Gruppe %@!</target>
|
||||
<trans-unit id="Member will be removed from chat - this cannot be undone!" xml:space="preserve">
|
||||
<source>Member will be removed from chat - this cannot be undone!</source>
|
||||
<target>Das Mitglied wird aus dem Chat entfernt. Dies kann nicht rückgängig gemacht werden!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member will be removed from group - this cannot be undone!" xml:space="preserve">
|
||||
<source>Member will be removed from group - this cannot be undone!</source>
|
||||
<target>Das Mitglied wird aus der Gruppe entfernt. Dies kann nicht rückgängig gemacht werden!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member will join the group, accept member?" xml:space="preserve">
|
||||
<source>Member will join the group, accept member?</source>
|
||||
@@ -6595,7 +6632,11 @@ swipe action</note>
|
||||
<trans-unit id="Remove" xml:space="preserve">
|
||||
<source>Remove</source>
|
||||
<target>Entfernen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove and delete messages" xml:space="preserve">
|
||||
<source>Remove and delete messages</source>
|
||||
<note>alert action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove archive?" xml:space="preserve">
|
||||
<source>Remove archive?</source>
|
||||
@@ -6620,7 +6661,7 @@ swipe action</note>
|
||||
<trans-unit id="Remove member?" xml:space="preserve">
|
||||
<source>Remove member?</source>
|
||||
<target>Das Mitglied entfernen?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove passphrase from keychain?" xml:space="preserve">
|
||||
<source>Remove passphrase from keychain?</source>
|
||||
@@ -7038,11 +7079,31 @@ chat item action</note>
|
||||
<target>In der Suchleiste werden nun auch Einladungslinks angenommen.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search files" xml:space="preserve">
|
||||
<source>Search files</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search images" xml:space="preserve">
|
||||
<source>Search images</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search links" xml:space="preserve">
|
||||
<source>Search links</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search or paste SimpleX link" xml:space="preserve">
|
||||
<source>Search or paste SimpleX link</source>
|
||||
<target>Suchen oder SimpleX-Link einfügen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search videos" xml:space="preserve">
|
||||
<source>Search videos</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search voice messages" xml:space="preserve">
|
||||
<source>Search voice messages</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Secondary" xml:space="preserve">
|
||||
<source>Secondary</source>
|
||||
<target>Zweite Farbe</target>
|
||||
@@ -8918,6 +8979,10 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
|
||||
<target>Das Video wird heruntergeladen, sobald Ihr Kontakt online ist. Bitte warten oder überprüfen Sie es später!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Videos" xml:space="preserve">
|
||||
<source>Videos</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Videos and files up to 1gb" xml:space="preserve">
|
||||
<source>Videos and files up to 1gb</source>
|
||||
<target>Videos und Dateien bis zu 1GB</target>
|
||||
|
||||
@@ -792,6 +792,11 @@ swipe action</note>
|
||||
<target>All group members will remain connected.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages" xml:space="preserve">
|
||||
<source>All messages</source>
|
||||
<target>All messages</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." xml:space="preserve">
|
||||
<source>All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.</source>
|
||||
<target>All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.</target>
|
||||
@@ -1142,6 +1147,11 @@ swipe action</note>
|
||||
<target>Audio and video calls</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Audio call" xml:space="preserve">
|
||||
<source>Audio call</source>
|
||||
<target>Audio call</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Audio/video calls" xml:space="preserve">
|
||||
<source>Audio/video calls</source>
|
||||
<target>Audio/video calls</target>
|
||||
@@ -2579,6 +2589,16 @@ swipe action</note>
|
||||
<target>Delete member message?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete member messages" xml:space="preserve">
|
||||
<source>Delete member messages</source>
|
||||
<target>Delete member messages</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete member messages?" xml:space="preserve">
|
||||
<source>Delete member messages?</source>
|
||||
<target>Delete member messages?</target>
|
||||
<note>alert title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete message?" xml:space="preserve">
|
||||
<source>Delete message?</source>
|
||||
<target>Delete message?</target>
|
||||
@@ -2587,7 +2607,8 @@ swipe action</note>
|
||||
<trans-unit id="Delete messages" xml:space="preserve">
|
||||
<source>Delete messages</source>
|
||||
<target>Delete messages</target>
|
||||
<note>alert button</note>
|
||||
<note>alert action
|
||||
alert button</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete messages after" xml:space="preserve">
|
||||
<source>Delete messages after</source>
|
||||
@@ -3851,6 +3872,11 @@ snd error text</note>
|
||||
<target>Files and media prohibited!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Filter" xml:space="preserve">
|
||||
<source>Filter</source>
|
||||
<target>Filter</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Filter unread and favorite chats." xml:space="preserve">
|
||||
<source>Filter unread and favorite chats.</source>
|
||||
<target>Filter unread and favorite chats.</target>
|
||||
@@ -4335,6 +4361,11 @@ Error: %2$@</target>
|
||||
<target>Image will be received when your contact is online, please wait or check later!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Images" xml:space="preserve">
|
||||
<source>Images</source>
|
||||
<target>Images</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Immediately" xml:space="preserve">
|
||||
<source>Immediately</source>
|
||||
<target>Immediately</target>
|
||||
@@ -4594,6 +4625,11 @@ More improvements are coming soon!</target>
|
||||
<target>Invite friends</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invite member" xml:space="preserve">
|
||||
<source>Invite member</source>
|
||||
<target>Invite member</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invite members" xml:space="preserve">
|
||||
<source>Invite members</source>
|
||||
<target>Invite members</target>
|
||||
@@ -4817,6 +4853,11 @@ This is your link for group %@!</target>
|
||||
<target>Linked desktops</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Links" xml:space="preserve">
|
||||
<source>Links</source>
|
||||
<target>Links</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="List" xml:space="preserve">
|
||||
<source>List</source>
|
||||
<target>List</target>
|
||||
@@ -4942,6 +4983,11 @@ This is your link for group %@!</target>
|
||||
<target>Member is deleted - can't accept request</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member messages will be deleted - this cannot be undone!" xml:space="preserve">
|
||||
<source>Member messages will be deleted - this cannot be undone!</source>
|
||||
<target>Member messages will be deleted - this cannot be undone!</target>
|
||||
<note>alert message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member reports" xml:space="preserve">
|
||||
<source>Member reports</source>
|
||||
<target>Member reports</target>
|
||||
@@ -4965,12 +5011,12 @@ This is your link for group %@!</target>
|
||||
<trans-unit id="Member will be removed from chat - this cannot be undone!" xml:space="preserve">
|
||||
<source>Member will be removed from chat - this cannot be undone!</source>
|
||||
<target>Member will be removed from chat - this cannot be undone!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member will be removed from group - this cannot be undone!" xml:space="preserve">
|
||||
<source>Member will be removed from group - this cannot be undone!</source>
|
||||
<target>Member will be removed from group - this cannot be undone!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member will join the group, accept member?" xml:space="preserve">
|
||||
<source>Member will join the group, accept member?</source>
|
||||
@@ -6595,7 +6641,12 @@ swipe action</note>
|
||||
<trans-unit id="Remove" xml:space="preserve">
|
||||
<source>Remove</source>
|
||||
<target>Remove</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove and delete messages" xml:space="preserve">
|
||||
<source>Remove and delete messages</source>
|
||||
<target>Remove and delete messages</target>
|
||||
<note>alert action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove archive?" xml:space="preserve">
|
||||
<source>Remove archive?</source>
|
||||
@@ -6620,7 +6671,7 @@ swipe action</note>
|
||||
<trans-unit id="Remove member?" xml:space="preserve">
|
||||
<source>Remove member?</source>
|
||||
<target>Remove member?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove passphrase from keychain?" xml:space="preserve">
|
||||
<source>Remove passphrase from keychain?</source>
|
||||
@@ -7038,11 +7089,36 @@ chat item action</note>
|
||||
<target>Search bar accepts invitation links.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search files" xml:space="preserve">
|
||||
<source>Search files</source>
|
||||
<target>Search files</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search images" xml:space="preserve">
|
||||
<source>Search images</source>
|
||||
<target>Search images</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search links" xml:space="preserve">
|
||||
<source>Search links</source>
|
||||
<target>Search links</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search or paste SimpleX link" xml:space="preserve">
|
||||
<source>Search or paste SimpleX link</source>
|
||||
<target>Search or paste SimpleX link</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search videos" xml:space="preserve">
|
||||
<source>Search videos</source>
|
||||
<target>Search videos</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search voice messages" xml:space="preserve">
|
||||
<source>Search voice messages</source>
|
||||
<target>Search voice messages</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Secondary" xml:space="preserve">
|
||||
<source>Secondary</source>
|
||||
<target>Secondary</target>
|
||||
@@ -8918,6 +8994,11 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>Video will be received when your contact is online, please wait or check later!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Videos" xml:space="preserve">
|
||||
<source>Videos</source>
|
||||
<target>Videos</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Videos and files up to 1gb" xml:space="preserve">
|
||||
<source>Videos and files up to 1gb</source>
|
||||
<target>Videos and files up to 1gb</target>
|
||||
|
||||
@@ -792,6 +792,10 @@ swipe action</note>
|
||||
<target>Todos los miembros del grupo permanecerán conectados.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages" xml:space="preserve">
|
||||
<source>All messages</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." xml:space="preserve">
|
||||
<source>All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.</source>
|
||||
<target>Todos los mensajes y archivos son enviados **cifrados de extremo a extremo** y con seguridad de cifrado postcuántico en mensajes directos.</target>
|
||||
@@ -1142,6 +1146,10 @@ swipe action</note>
|
||||
<target>Llamadas y videollamadas</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Audio call" xml:space="preserve">
|
||||
<source>Audio call</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Audio/video calls" xml:space="preserve">
|
||||
<source>Audio/video calls</source>
|
||||
<target>Llamadas y videollamadas</target>
|
||||
@@ -2579,6 +2587,14 @@ swipe action</note>
|
||||
<target>¿Eliminar el mensaje de miembro?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete member messages" xml:space="preserve">
|
||||
<source>Delete member messages</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete member messages?" xml:space="preserve">
|
||||
<source>Delete member messages?</source>
|
||||
<note>alert title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete message?" xml:space="preserve">
|
||||
<source>Delete message?</source>
|
||||
<target>¿Eliminar mensaje?</target>
|
||||
@@ -2587,7 +2603,8 @@ swipe action</note>
|
||||
<trans-unit id="Delete messages" xml:space="preserve">
|
||||
<source>Delete messages</source>
|
||||
<target>Activar</target>
|
||||
<note>alert button</note>
|
||||
<note>alert action
|
||||
alert button</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete messages after" xml:space="preserve">
|
||||
<source>Delete messages after</source>
|
||||
@@ -3851,6 +3868,10 @@ snd error text</note>
|
||||
<target>¡Archivos y multimedia no permitidos!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Filter" xml:space="preserve">
|
||||
<source>Filter</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Filter unread and favorite chats." xml:space="preserve">
|
||||
<source>Filter unread and favorite chats.</source>
|
||||
<target>Filtra chats no leídos y favoritos.</target>
|
||||
@@ -4335,6 +4356,10 @@ Error: %2$@</target>
|
||||
<target>La imagen se recibirá cuando el contacto esté en línea, ¡por favor espera o revisa más tarde!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Images" xml:space="preserve">
|
||||
<source>Images</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Immediately" xml:space="preserve">
|
||||
<source>Immediately</source>
|
||||
<target>Inmediatamente</target>
|
||||
@@ -4594,6 +4619,10 @@ More improvements are coming soon!</source>
|
||||
<target>Invitar amigos</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invite member" xml:space="preserve">
|
||||
<source>Invite member</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invite members" xml:space="preserve">
|
||||
<source>Invite members</source>
|
||||
<target>Invitar miembros</target>
|
||||
@@ -4817,6 +4846,10 @@ This is your link for group %@!</source>
|
||||
<target>Ordenadores enlazados</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Links" xml:space="preserve">
|
||||
<source>Links</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="List" xml:space="preserve">
|
||||
<source>List</source>
|
||||
<target>Lista</target>
|
||||
@@ -4942,6 +4975,10 @@ This is your link for group %@!</source>
|
||||
<target>Miembro eliminado, no puede aceptar solicitudes</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member messages will be deleted - this cannot be undone!" xml:space="preserve">
|
||||
<source>Member messages will be deleted - this cannot be undone!</source>
|
||||
<note>alert message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member reports" xml:space="preserve">
|
||||
<source>Member reports</source>
|
||||
<target>Informes de miembros</target>
|
||||
@@ -4965,12 +5002,12 @@ This is your link for group %@!</source>
|
||||
<trans-unit id="Member will be removed from chat - this cannot be undone!" xml:space="preserve">
|
||||
<source>Member will be removed from chat - this cannot be undone!</source>
|
||||
<target>El miembro será eliminado del chat. ¡No puede deshacerse!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member will be removed from group - this cannot be undone!" xml:space="preserve">
|
||||
<source>Member will be removed from group - this cannot be undone!</source>
|
||||
<target>El miembro será expulsado del grupo. ¡No puede deshacerse!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member will join the group, accept member?" xml:space="preserve">
|
||||
<source>Member will join the group, accept member?</source>
|
||||
@@ -5484,7 +5521,7 @@ This is your link for group %@!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="No direct connection yet, message is forwarded by admin." xml:space="preserve">
|
||||
<source>No direct connection yet, message is forwarded by admin.</source>
|
||||
<target>Aún no hay conexión directa con este miembro, el mensaje es reenviado por el administrador.</target>
|
||||
<target>Aún no hay conexión directa, los mensajes son reenviados por el administrador.</target>
|
||||
<note>item status description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No filtered chats" xml:space="preserve">
|
||||
@@ -6595,7 +6632,11 @@ swipe action</note>
|
||||
<trans-unit id="Remove" xml:space="preserve">
|
||||
<source>Remove</source>
|
||||
<target>Eliminar</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove and delete messages" xml:space="preserve">
|
||||
<source>Remove and delete messages</source>
|
||||
<note>alert action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove archive?" xml:space="preserve">
|
||||
<source>Remove archive?</source>
|
||||
@@ -6620,7 +6661,7 @@ swipe action</note>
|
||||
<trans-unit id="Remove member?" xml:space="preserve">
|
||||
<source>Remove member?</source>
|
||||
<target>¿Expulsar miembro?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove passphrase from keychain?" xml:space="preserve">
|
||||
<source>Remove passphrase from keychain?</source>
|
||||
@@ -7038,11 +7079,31 @@ chat item action</note>
|
||||
<target>La barra de búsqueda acepta enlaces de invitación.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search files" xml:space="preserve">
|
||||
<source>Search files</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search images" xml:space="preserve">
|
||||
<source>Search images</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search links" xml:space="preserve">
|
||||
<source>Search links</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search or paste SimpleX link" xml:space="preserve">
|
||||
<source>Search or paste SimpleX link</source>
|
||||
<target>Buscar o pegar enlace SimpleX</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search videos" xml:space="preserve">
|
||||
<source>Search videos</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search voice messages" xml:space="preserve">
|
||||
<source>Search voice messages</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Secondary" xml:space="preserve">
|
||||
<source>Secondary</source>
|
||||
<target>Secundario</target>
|
||||
@@ -8918,6 +8979,10 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión
|
||||
<target>El vídeo se recibirá cuando el contacto esté en línea, por favor espera o revisa más tarde.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Videos" xml:space="preserve">
|
||||
<source>Videos</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Videos and files up to 1gb" xml:space="preserve">
|
||||
<source>Videos and files up to 1gb</source>
|
||||
<target>Vídeos y archivos de hasta 1Gb</target>
|
||||
@@ -9649,7 +9714,7 @@ Repeat connection request?</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="accepted you" xml:space="preserve">
|
||||
<source>accepted you</source>
|
||||
<target>te ha aceptado</target>
|
||||
<target>te ha admitido</target>
|
||||
<note>rcv group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="admin" xml:space="preserve">
|
||||
@@ -10623,7 +10688,7 @@ last received msg: %2$@</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="you accepted this member" xml:space="preserve">
|
||||
<source>you accepted this member</source>
|
||||
<target>has aceptado al miembro</target>
|
||||
<target>has admitido al miembro</target>
|
||||
<note>snd group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="you are observer" xml:space="preserve">
|
||||
|
||||
@@ -726,6 +726,10 @@ swipe action</note>
|
||||
<target>Kaikki ryhmän jäsenet pysyvät yhteydessä.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages" xml:space="preserve">
|
||||
<source>All messages</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." xml:space="preserve">
|
||||
<source>All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -1042,6 +1046,10 @@ swipe action</note>
|
||||
<target>Ääni- ja videopuhelut</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Audio call" xml:space="preserve">
|
||||
<source>Audio call</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Audio/video calls" xml:space="preserve">
|
||||
<source>Audio/video calls</source>
|
||||
<target>Ääni/videopuhelut</target>
|
||||
@@ -2328,6 +2336,14 @@ swipe action</note>
|
||||
<target>Poista jäsenviesti?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete member messages" xml:space="preserve">
|
||||
<source>Delete member messages</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete member messages?" xml:space="preserve">
|
||||
<source>Delete member messages?</source>
|
||||
<note>alert title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete message?" xml:space="preserve">
|
||||
<source>Delete message?</source>
|
||||
<target>Poista viesti?</target>
|
||||
@@ -2336,7 +2352,8 @@ swipe action</note>
|
||||
<trans-unit id="Delete messages" xml:space="preserve">
|
||||
<source>Delete messages</source>
|
||||
<target>Poista viestit</target>
|
||||
<note>alert button</note>
|
||||
<note>alert action
|
||||
alert button</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete messages after" xml:space="preserve">
|
||||
<source>Delete messages after</source>
|
||||
@@ -3483,6 +3500,10 @@ snd error text</note>
|
||||
<target>Tiedostot ja media kielletty!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Filter" xml:space="preserve">
|
||||
<source>Filter</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Filter unread and favorite chats." xml:space="preserve">
|
||||
<source>Filter unread and favorite chats.</source>
|
||||
<target>Suodata lukemattomia- ja suosikkikeskusteluja.</target>
|
||||
@@ -3924,6 +3945,10 @@ Error: %2$@</source>
|
||||
<target>Kuva vastaanotetaan, kun kontaktisi on verkossa, odota tai tarkista myöhemmin!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Images" xml:space="preserve">
|
||||
<source>Images</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Immediately" xml:space="preserve">
|
||||
<source>Immediately</source>
|
||||
<target>Heti</target>
|
||||
@@ -4159,6 +4184,10 @@ More improvements are coming soon!</source>
|
||||
<target>Kutsu ystäviä</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invite member" xml:space="preserve">
|
||||
<source>Invite member</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invite members" xml:space="preserve">
|
||||
<source>Invite members</source>
|
||||
<target>Kutsu jäseniä</target>
|
||||
@@ -4366,6 +4395,10 @@ This is your link for group %@!</source>
|
||||
<source>Linked desktops</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Links" xml:space="preserve">
|
||||
<source>Links</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="List" xml:space="preserve">
|
||||
<source>List</source>
|
||||
<note>swipe action</note>
|
||||
@@ -4481,6 +4514,10 @@ This is your link for group %@!</source>
|
||||
<source>Member is deleted - can't accept request</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member messages will be deleted - this cannot be undone!" xml:space="preserve">
|
||||
<source>Member messages will be deleted - this cannot be undone!</source>
|
||||
<note>alert message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member reports" xml:space="preserve">
|
||||
<source>Member reports</source>
|
||||
<note>chat feature</note>
|
||||
@@ -4501,12 +4538,12 @@ This is your link for group %@!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member will be removed from chat - this cannot be undone!" xml:space="preserve">
|
||||
<source>Member will be removed from chat - this cannot be undone!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member will be removed from group - this cannot be undone!" xml:space="preserve">
|
||||
<source>Member will be removed from group - this cannot be undone!</source>
|
||||
<target>Jäsen poistetaan ryhmästä - tätä ei voi perua!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member will join the group, accept member?" xml:space="preserve">
|
||||
<source>Member will join the group, accept member?</source>
|
||||
@@ -5959,7 +5996,11 @@ swipe action</note>
|
||||
<trans-unit id="Remove" xml:space="preserve">
|
||||
<source>Remove</source>
|
||||
<target>Poista</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove and delete messages" xml:space="preserve">
|
||||
<source>Remove and delete messages</source>
|
||||
<note>alert action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove archive?" xml:space="preserve">
|
||||
<source>Remove archive?</source>
|
||||
@@ -5981,7 +6022,7 @@ swipe action</note>
|
||||
<trans-unit id="Remove member?" xml:space="preserve">
|
||||
<source>Remove member?</source>
|
||||
<target>Poista jäsen?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove passphrase from keychain?" xml:space="preserve">
|
||||
<source>Remove passphrase from keychain?</source>
|
||||
@@ -6356,10 +6397,30 @@ chat item action</note>
|
||||
<source>Search bar accepts invitation links.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search files" xml:space="preserve">
|
||||
<source>Search files</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search images" xml:space="preserve">
|
||||
<source>Search images</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search links" xml:space="preserve">
|
||||
<source>Search links</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search or paste SimpleX link" xml:space="preserve">
|
||||
<source>Search or paste SimpleX link</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search videos" xml:space="preserve">
|
||||
<source>Search videos</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search voice messages" xml:space="preserve">
|
||||
<source>Search voice messages</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Secondary" xml:space="preserve">
|
||||
<source>Secondary</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -8038,6 +8099,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
|
||||
<target>Video vastaanotetaan, kun kontaktisi on online-tilassa, odota tai tarkista myöhemmin!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Videos" xml:space="preserve">
|
||||
<source>Videos</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Videos and files up to 1gb" xml:space="preserve">
|
||||
<source>Videos and files up to 1gb</source>
|
||||
<target>Videot ja tiedostot 1 Gt asti</target>
|
||||
|
||||
@@ -792,6 +792,10 @@ swipe action</note>
|
||||
<target>Tous les membres du groupe resteront connectés.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages" xml:space="preserve">
|
||||
<source>All messages</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." xml:space="preserve">
|
||||
<source>All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.</source>
|
||||
<target>Tous les messages et fichiers sont envoyés **chiffrés de bout en bout**, avec une sécurité post-quantique dans les messages directs.</target>
|
||||
@@ -1141,6 +1145,10 @@ swipe action</note>
|
||||
<target>Appels audio et vidéo</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Audio call" xml:space="preserve">
|
||||
<source>Audio call</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Audio/video calls" xml:space="preserve">
|
||||
<source>Audio/video calls</source>
|
||||
<target>Appels audio/vidéo</target>
|
||||
@@ -2564,6 +2572,14 @@ swipe action</note>
|
||||
<target>Supprimer le message de ce membre ?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete member messages" xml:space="preserve">
|
||||
<source>Delete member messages</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete member messages?" xml:space="preserve">
|
||||
<source>Delete member messages?</source>
|
||||
<note>alert title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete message?" xml:space="preserve">
|
||||
<source>Delete message?</source>
|
||||
<target>Supprimer le message ?</target>
|
||||
@@ -2572,7 +2588,8 @@ swipe action</note>
|
||||
<trans-unit id="Delete messages" xml:space="preserve">
|
||||
<source>Delete messages</source>
|
||||
<target>Supprimer les messages</target>
|
||||
<note>alert button</note>
|
||||
<note>alert action
|
||||
alert button</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete messages after" xml:space="preserve">
|
||||
<source>Delete messages after</source>
|
||||
@@ -3822,6 +3839,10 @@ snd error text</note>
|
||||
<target>Fichiers et médias interdits !</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Filter" xml:space="preserve">
|
||||
<source>Filter</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Filter unread and favorite chats." xml:space="preserve">
|
||||
<source>Filter unread and favorite chats.</source>
|
||||
<target>Filtrer les messages non lus et favoris.</target>
|
||||
@@ -4296,6 +4317,10 @@ Erreur : %2$@</target>
|
||||
<target>L'image sera reçue quand votre contact sera en ligne, merci d'attendre ou de revenir plus tard !</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Images" xml:space="preserve">
|
||||
<source>Images</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Immediately" xml:space="preserve">
|
||||
<source>Immediately</source>
|
||||
<target>Immédiatement</target>
|
||||
@@ -4548,6 +4573,10 @@ D'autres améliorations sont à venir !</target>
|
||||
<target>Inviter des amis</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invite member" xml:space="preserve">
|
||||
<source>Invite member</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invite members" xml:space="preserve">
|
||||
<source>Invite members</source>
|
||||
<target>Inviter des membres</target>
|
||||
@@ -4769,6 +4798,10 @@ Voici votre lien pour le groupe %@ !</target>
|
||||
<target>Bureaux liés</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Links" xml:space="preserve">
|
||||
<source>Links</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="List" xml:space="preserve">
|
||||
<source>List</source>
|
||||
<note>swipe action</note>
|
||||
@@ -4887,6 +4920,10 @@ Voici votre lien pour le groupe %@ !</target>
|
||||
<source>Member is deleted - can't accept request</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member messages will be deleted - this cannot be undone!" xml:space="preserve">
|
||||
<source>Member messages will be deleted - this cannot be undone!</source>
|
||||
<note>alert message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member reports" xml:space="preserve">
|
||||
<source>Member reports</source>
|
||||
<note>chat feature</note>
|
||||
@@ -4909,12 +4946,12 @@ Voici votre lien pour le groupe %@ !</target>
|
||||
<trans-unit id="Member will be removed from chat - this cannot be undone!" xml:space="preserve">
|
||||
<source>Member will be removed from chat - this cannot be undone!</source>
|
||||
<target>Le membre sera retiré de la discussion - cela ne peut pas être annulé !</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member will be removed from group - this cannot be undone!" xml:space="preserve">
|
||||
<source>Member will be removed from group - this cannot be undone!</source>
|
||||
<target>Ce membre sera retiré du groupe - impossible de revenir en arrière !</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member will join the group, accept member?" xml:space="preserve">
|
||||
<source>Member will join the group, accept member?</source>
|
||||
@@ -6489,7 +6526,11 @@ swipe action</note>
|
||||
<trans-unit id="Remove" xml:space="preserve">
|
||||
<source>Remove</source>
|
||||
<target>Supprimer</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove and delete messages" xml:space="preserve">
|
||||
<source>Remove and delete messages</source>
|
||||
<note>alert action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove archive?" xml:space="preserve">
|
||||
<source>Remove archive?</source>
|
||||
@@ -6513,7 +6554,7 @@ swipe action</note>
|
||||
<trans-unit id="Remove member?" xml:space="preserve">
|
||||
<source>Remove member?</source>
|
||||
<target>Retirer ce membre ?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove passphrase from keychain?" xml:space="preserve">
|
||||
<source>Remove passphrase from keychain?</source>
|
||||
@@ -6912,11 +6953,31 @@ chat item action</note>
|
||||
<target>La barre de recherche accepte les liens d'invitation.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search files" xml:space="preserve">
|
||||
<source>Search files</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search images" xml:space="preserve">
|
||||
<source>Search images</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search links" xml:space="preserve">
|
||||
<source>Search links</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search or paste SimpleX link" xml:space="preserve">
|
||||
<source>Search or paste SimpleX link</source>
|
||||
<target>Rechercher ou coller un lien SimpleX</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search videos" xml:space="preserve">
|
||||
<source>Search videos</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search voice messages" xml:space="preserve">
|
||||
<source>Search voice messages</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Secondary" xml:space="preserve">
|
||||
<source>Secondary</source>
|
||||
<target>Secondaire</target>
|
||||
@@ -8743,6 +8804,10 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien
|
||||
<target>La vidéo ne sera reçue que lorsque votre contact sera en ligne. Veuillez patienter ou vérifier plus tard !</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Videos" xml:space="preserve">
|
||||
<source>Videos</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Videos and files up to 1gb" xml:space="preserve">
|
||||
<source>Videos and files up to 1gb</source>
|
||||
<target>Vidéos et fichiers jusqu'à 1Go</target>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -792,6 +792,10 @@ swipe action</note>
|
||||
<target>Tutti i membri del gruppo resteranno connessi.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages" xml:space="preserve">
|
||||
<source>All messages</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." xml:space="preserve">
|
||||
<source>All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.</source>
|
||||
<target>Tutti i messaggi e i file vengono inviati **crittografati end-to-end**, con sicurezza resistenti alla quantistica nei messaggi diretti.</target>
|
||||
@@ -1142,6 +1146,10 @@ swipe action</note>
|
||||
<target>Chiamate audio e video</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Audio call" xml:space="preserve">
|
||||
<source>Audio call</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Audio/video calls" xml:space="preserve">
|
||||
<source>Audio/video calls</source>
|
||||
<target>Chiamate audio/video</target>
|
||||
@@ -2579,6 +2587,14 @@ swipe action</note>
|
||||
<target>Eliminare il messaggio del membro?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete member messages" xml:space="preserve">
|
||||
<source>Delete member messages</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete member messages?" xml:space="preserve">
|
||||
<source>Delete member messages?</source>
|
||||
<note>alert title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete message?" xml:space="preserve">
|
||||
<source>Delete message?</source>
|
||||
<target>Eliminare il messaggio?</target>
|
||||
@@ -2587,7 +2603,8 @@ swipe action</note>
|
||||
<trans-unit id="Delete messages" xml:space="preserve">
|
||||
<source>Delete messages</source>
|
||||
<target>Elimina messaggi</target>
|
||||
<note>alert button</note>
|
||||
<note>alert action
|
||||
alert button</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete messages after" xml:space="preserve">
|
||||
<source>Delete messages after</source>
|
||||
@@ -3851,6 +3868,10 @@ snd error text</note>
|
||||
<target>File e contenuti multimediali vietati!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Filter" xml:space="preserve">
|
||||
<source>Filter</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Filter unread and favorite chats." xml:space="preserve">
|
||||
<source>Filter unread and favorite chats.</source>
|
||||
<target>Filtra le chat non lette e preferite.</target>
|
||||
@@ -4335,6 +4356,10 @@ Errore: %2$@</target>
|
||||
<target>L'immagine verrà ricevuta quando il tuo contatto sarà in linea, aspetta o controlla più tardi!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Images" xml:space="preserve">
|
||||
<source>Images</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Immediately" xml:space="preserve">
|
||||
<source>Immediately</source>
|
||||
<target>Immediatamente</target>
|
||||
@@ -4594,6 +4619,10 @@ Altri miglioramenti sono in arrivo!</target>
|
||||
<target>Invita amici</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invite member" xml:space="preserve">
|
||||
<source>Invite member</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invite members" xml:space="preserve">
|
||||
<source>Invite members</source>
|
||||
<target>Invita membri</target>
|
||||
@@ -4817,6 +4846,10 @@ Questo è il tuo link per il gruppo %@!</target>
|
||||
<target>Desktop collegati</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Links" xml:space="preserve">
|
||||
<source>Links</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="List" xml:space="preserve">
|
||||
<source>List</source>
|
||||
<target>Elenco</target>
|
||||
@@ -4942,6 +4975,10 @@ Questo è il tuo link per il gruppo %@!</target>
|
||||
<target>Il membro è eliminato - impossibile accettare la richiesta</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member messages will be deleted - this cannot be undone!" xml:space="preserve">
|
||||
<source>Member messages will be deleted - this cannot be undone!</source>
|
||||
<note>alert message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member reports" xml:space="preserve">
|
||||
<source>Member reports</source>
|
||||
<target>Segnalazioni dei membri</target>
|
||||
@@ -4965,12 +5002,12 @@ Questo è il tuo link per il gruppo %@!</target>
|
||||
<trans-unit id="Member will be removed from chat - this cannot be undone!" xml:space="preserve">
|
||||
<source>Member will be removed from chat - this cannot be undone!</source>
|
||||
<target>Il membro verrà rimosso dalla chat, non è reversibile!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member will be removed from group - this cannot be undone!" xml:space="preserve">
|
||||
<source>Member will be removed from group - this cannot be undone!</source>
|
||||
<target>Il membro verrà rimosso dal gruppo, non è reversibile!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Member will join the group, accept member?" xml:space="preserve">
|
||||
<source>Member will join the group, accept member?</source>
|
||||
@@ -5854,7 +5891,7 @@ Richiede l'attivazione della VPN.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open new group" xml:space="preserve">
|
||||
<source>Open new group</source>
|
||||
<target>Apri un gruppo nuovo</target>
|
||||
<target>Apri il nuovo gruppo</target>
|
||||
<note>new chat action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open to accept" xml:space="preserve">
|
||||
@@ -6595,7 +6632,11 @@ swipe action</note>
|
||||
<trans-unit id="Remove" xml:space="preserve">
|
||||
<source>Remove</source>
|
||||
<target>Rimuovi</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove and delete messages" xml:space="preserve">
|
||||
<source>Remove and delete messages</source>
|
||||
<note>alert action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove archive?" xml:space="preserve">
|
||||
<source>Remove archive?</source>
|
||||
@@ -6620,7 +6661,7 @@ swipe action</note>
|
||||
<trans-unit id="Remove member?" xml:space="preserve">
|
||||
<source>Remove member?</source>
|
||||
<target>Rimuovere il membro?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>alert title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove passphrase from keychain?" xml:space="preserve">
|
||||
<source>Remove passphrase from keychain?</source>
|
||||
@@ -7038,11 +7079,31 @@ chat item action</note>
|
||||
<target>La barra di ricerca accetta i link di invito.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search files" xml:space="preserve">
|
||||
<source>Search files</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search images" xml:space="preserve">
|
||||
<source>Search images</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search links" xml:space="preserve">
|
||||
<source>Search links</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search or paste SimpleX link" xml:space="preserve">
|
||||
<source>Search or paste SimpleX link</source>
|
||||
<target>Cerca o incolla un link SimpleX</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search videos" xml:space="preserve">
|
||||
<source>Search videos</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search voice messages" xml:space="preserve">
|
||||
<source>Search voice messages</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Secondary" xml:space="preserve">
|
||||
<source>Secondary</source>
|
||||
<target>Secondario</target>
|
||||
@@ -8918,6 +8979,10 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
|
||||
<target>Il video verrà ricevuto quando il tuo contatto sarà in linea, attendi o controlla più tardi!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Videos" xml:space="preserve">
|
||||
<source>Videos</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Videos and files up to 1gb" xml:space="preserve">
|
||||
<source>Videos and files up to 1gb</source>
|
||||
<target>Video e file fino a 1 GB</target>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user