Compare commits
170 Commits
v5.4.3-arm
...
av/ios-rem
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fab0c435d1 | ||
|
|
1195ee5b30 | ||
|
|
b012ae5c7c | ||
|
|
7213913d51 | ||
|
|
5807a0a2fa | ||
|
|
e6e27db243 | ||
|
|
b629c22ee0 | ||
|
|
f7d7f5461f | ||
|
|
c544a636f6 | ||
|
|
6d523d5b4b | ||
|
|
2a321b3ff8 | ||
|
|
865a32c608 | ||
|
|
91f10c056f | ||
|
|
fec34ca875 | ||
|
|
3a0920e950 | ||
|
|
7e9e71ffbd | ||
|
|
09bbaa1c94 | ||
|
|
3a879b755b | ||
|
|
c6f4d62d6c | ||
|
|
5ad356172f | ||
|
|
0a6c464a8d | ||
|
|
1aa464bdb2 | ||
|
|
bce829ef58 | ||
|
|
7df300cf36 | ||
|
|
9b35ff6f11 | ||
|
|
c4cbb49f57 | ||
|
|
2b3eebb7a2 | ||
|
|
a1328c287c | ||
|
|
f2d498dd79 | ||
|
|
6b8fc6fdcf | ||
|
|
0e585d5e5b | ||
|
|
520d8868ef | ||
|
|
7192448303 | ||
|
|
0f0f65533a | ||
|
|
78a38cb080 | ||
|
|
f102f39147 | ||
|
|
cd349e80ce | ||
|
|
430dc5bd2e | ||
|
|
3e0b863b64 | ||
|
|
d6afee11bc | ||
|
|
6ef3a9e668 | ||
|
|
fbe3353434 | ||
|
|
d6d2e6e1eb | ||
|
|
14d5078404 | ||
|
|
da1d20c17f | ||
|
|
afc324dc4f | ||
|
|
f81e457e09 | ||
|
|
bd30b80e15 | ||
|
|
da9a7f4642 | ||
|
|
838a759a76 | ||
|
|
f1ff27218c | ||
|
|
8738cf332f | ||
|
|
4f602d4571 | ||
|
|
d0b75e0dd3 | ||
|
|
1bb98706a6 | ||
|
|
6b7c13f20a | ||
|
|
93d2bb98b5 | ||
|
|
3b614e95e3 | ||
|
|
1a9e942e33 | ||
|
|
bddb9c14b7 | ||
|
|
afd145c979 | ||
|
|
97fbf2b7fe | ||
|
|
db93d29e76 | ||
|
|
10b3fe1390 | ||
|
|
a2b9a04430 | ||
|
|
444b92282e | ||
|
|
c35d275273 | ||
|
|
052ef2037a | ||
|
|
0b731d818b | ||
|
|
1aab9f7f8a | ||
|
|
bdf3ac1a36 | ||
|
|
8159ae4aea | ||
|
|
a9ba0a2e8a | ||
|
|
c65a17fccb | ||
|
|
5aa4b7687e | ||
|
|
1016513dd3 | ||
|
|
106556812c | ||
|
|
7d2f6a3609 | ||
|
|
f188118643 | ||
|
|
cc05434b31 | ||
|
|
68de2b7540 | ||
|
|
5d8bb24d1c | ||
|
|
ab9a6dcab5 | ||
|
|
0dd1f13d3b | ||
|
|
16f53490c5 | ||
|
|
4e502c4d13 | ||
|
|
5236e0f201 | ||
|
|
b69b72a93c | ||
|
|
2dae9180ec | ||
|
|
ec57529f12 | ||
|
|
c4d75366b5 | ||
|
|
ddcd7d495e | ||
|
|
b5fe1f8364 | ||
|
|
3c7e37ee9d | ||
|
|
2884ad9fde | ||
|
|
c130905efa | ||
|
|
915dc46bdc | ||
|
|
1eb1ed92f7 | ||
|
|
236daf4391 | ||
|
|
13feffb33a | ||
|
|
06d36751d7 | ||
|
|
c25e9e3b72 | ||
|
|
868acd18d6 | ||
|
|
8c1114648a | ||
|
|
3918c5306d | ||
|
|
ab8a87acad | ||
|
|
809dd1ef01 | ||
|
|
b874cd1910 | ||
|
|
300223b32e | ||
|
|
88640b85c4 | ||
|
|
d5cf9fbf5b | ||
|
|
f4f8501eb8 | ||
|
|
be0c791c43 | ||
|
|
46fe0fc671 | ||
|
|
bfb274b037 | ||
|
|
8d7dcb550a | ||
|
|
135e735dd4 | ||
|
|
516410a899 | ||
|
|
dad9716915 | ||
|
|
bc8a6f4833 | ||
|
|
5b7a09f488 | ||
|
|
bfe5d51df7 | ||
|
|
d9d270f00e | ||
|
|
2872b30e8c | ||
|
|
f45b24367c | ||
|
|
9e369c3ac9 | ||
|
|
afbb3b4e4b | ||
|
|
acd05c43db | ||
|
|
61b14b22d5 | ||
|
|
25a4719414 | ||
|
|
f802ec75fe | ||
|
|
283c90f5ae | ||
|
|
99a9fb2e1f | ||
|
|
ce9d583b39 | ||
|
|
fc56873f1c | ||
|
|
a30da38af7 | ||
|
|
0bf3d054c6 | ||
|
|
a55a8b116a | ||
|
|
7a207fd641 | ||
|
|
4508e0dfc1 | ||
|
|
a853ba3a15 | ||
|
|
61c507e7da | ||
|
|
64230f3545 | ||
|
|
3428f4d2ee | ||
|
|
fe865c5e11 | ||
|
|
9e87fe73a5 | ||
|
|
0ef2c55983 | ||
|
|
8882284fb7 | ||
|
|
767522e701 | ||
|
|
d009777901 | ||
|
|
f758a5526a | ||
|
|
e6b5727003 | ||
|
|
c9b1d54f13 | ||
|
|
05065e919b | ||
|
|
5399212e48 | ||
|
|
809040c7bc | ||
|
|
4ab078bd18 | ||
|
|
644169b835 | ||
|
|
78eefee6cc | ||
|
|
e253c55ba4 | ||
|
|
478bb32cdb | ||
|
|
1438fd00e2 | ||
|
|
05b55d3fb5 | ||
|
|
9c061508a4 | ||
|
|
2bacc00a06 | ||
|
|
d637714963 | ||
|
|
5ff6bd15f6 | ||
|
|
5b52d0e173 | ||
|
|
b4dd6941d7 | ||
|
|
07334b057d |
75
.github/workflows/build.yml
vendored
75
.github/workflows/build.yml
vendored
@@ -9,6 +9,7 @@ on:
|
||||
tags:
|
||||
- "v*"
|
||||
- "!*-fdroid"
|
||||
- "!*-armv7a"
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
@@ -42,7 +43,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
build:
|
||||
name: build-${{ matrix.os }}
|
||||
name: build-${{ matrix.os }}-${{ matrix.ghc }}
|
||||
if: always()
|
||||
needs: prepare-release
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -51,18 +52,25 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-20.04
|
||||
ghc: "8.10.7"
|
||||
cache_path: ~/.cabal/store
|
||||
- os: ubuntu-20.04
|
||||
ghc: "9.6.3"
|
||||
cache_path: ~/.cabal/store
|
||||
asset_name: simplex-chat-ubuntu-20_04-x86-64
|
||||
desktop_asset_name: simplex-desktop-ubuntu-20_04-x86_64.deb
|
||||
- os: ubuntu-22.04
|
||||
ghc: "9.6.3"
|
||||
cache_path: ~/.cabal/store
|
||||
asset_name: simplex-chat-ubuntu-22_04-x86-64
|
||||
desktop_asset_name: simplex-desktop-ubuntu-22_04-x86_64.deb
|
||||
- os: macos-latest
|
||||
ghc: "9.6.3"
|
||||
cache_path: ~/.cabal/store
|
||||
asset_name: simplex-chat-macos-x86-64
|
||||
desktop_asset_name: simplex-desktop-macos-x86_64.dmg
|
||||
- os: windows-latest
|
||||
ghc: "9.6.3"
|
||||
cache_path: C:/cabal
|
||||
asset_name: simplex-chat-windows-x86-64
|
||||
desktop_asset_name: simplex-desktop-windows-x86_64.msi
|
||||
@@ -79,18 +87,19 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Haskell
|
||||
uses: haskell/actions/setup@v2
|
||||
uses: haskell-actions/setup@v2
|
||||
with:
|
||||
ghc-version: "8.10.7"
|
||||
cabal-version: "latest"
|
||||
ghc-version: ${{ matrix.ghc }}
|
||||
cabal-version: "3.10.1.0"
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v3
|
||||
- name: Restore cached build
|
||||
id: restore_cache
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: |
|
||||
${{ matrix.cache_path }}
|
||||
dist-newstyle
|
||||
key: ${{ matrix.os }}-${{ hashFiles('cabal.project', 'simplex-chat.cabal') }}
|
||||
key: ${{ matrix.os }}-ghc${{ matrix.ghc }}-${{ hashFiles('cabal.project', 'simplex-chat.cabal') }}
|
||||
|
||||
# / Unix
|
||||
|
||||
@@ -105,7 +114,7 @@ jobs:
|
||||
echo " flags: +openssl" >> cabal.project.local
|
||||
|
||||
- name: Install AppImage dependencies
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'ubuntu-20.04'
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os == 'ubuntu-20.04'
|
||||
run: sudo apt install -y desktop-file-utils
|
||||
|
||||
- name: Install pkg-config for Mac
|
||||
@@ -131,7 +140,7 @@ jobs:
|
||||
echo "bin_hash=$(echo SHA2-512\(${{ matrix.asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Unix upload CLI binary to release
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.os != 'windows-latest'
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os != 'windows-latest'
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -140,7 +149,7 @@ jobs:
|
||||
tag: ${{ github.ref }}
|
||||
|
||||
- name: Unix update CLI binary hash
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.os != 'windows-latest'
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os != 'windows-latest'
|
||||
uses: softprops/action-gh-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -150,7 +159,7 @@ jobs:
|
||||
${{ steps.unix_cli_build.outputs.bin_hash }}
|
||||
|
||||
- name: Setup Java
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'corretto'
|
||||
@@ -159,7 +168,7 @@ jobs:
|
||||
|
||||
- name: Linux build desktop
|
||||
id: linux_desktop_build
|
||||
if: startsWith(github.ref, 'refs/tags/v') && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04')
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04')
|
||||
shell: bash
|
||||
run: |
|
||||
scripts/desktop/build-lib-linux.sh
|
||||
@@ -168,10 +177,10 @@ jobs:
|
||||
path=$(echo $PWD/release/main/deb/simplex_*_amd64.deb)
|
||||
echo "package_path=$path" >> $GITHUB_OUTPUT
|
||||
echo "package_hash=$(echo SHA2-512\(${{ matrix.desktop_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT
|
||||
|
||||
|
||||
- name: Linux make AppImage
|
||||
id: linux_appimage_build
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'ubuntu-20.04'
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os == 'ubuntu-20.04'
|
||||
shell: bash
|
||||
run: |
|
||||
scripts/desktop/make-appimage-linux.sh
|
||||
@@ -188,13 +197,13 @@ jobs:
|
||||
APPLE_SIMPLEX_NOTARIZATION_APPLE_ID: ${{ secrets.APPLE_SIMPLEX_NOTARIZATION_APPLE_ID }}
|
||||
APPLE_SIMPLEX_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_SIMPLEX_NOTARIZATION_PASSWORD }}
|
||||
run: |
|
||||
scripts/build-desktop-mac.sh
|
||||
scripts/ci/build-desktop-mac.sh
|
||||
path=$(echo $PWD/apps/multiplatform/release/main/dmg/SimpleX-*.dmg)
|
||||
echo "package_path=$path" >> $GITHUB_OUTPUT
|
||||
echo "package_hash=$(echo SHA2-512\(${{ matrix.desktop_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Linux upload desktop package to release
|
||||
if: startsWith(github.ref, 'refs/tags/v') && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04')
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04')
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -203,7 +212,7 @@ jobs:
|
||||
tag: ${{ github.ref }}
|
||||
|
||||
- name: Linux update desktop package hash
|
||||
if: startsWith(github.ref, 'refs/tags/v') && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04')
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04')
|
||||
uses: softprops/action-gh-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -213,7 +222,7 @@ jobs:
|
||||
${{ steps.linux_desktop_build.outputs.package_hash }}
|
||||
|
||||
- name: Linux upload AppImage to release
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'ubuntu-20.04'
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os == 'ubuntu-20.04'
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -222,7 +231,7 @@ jobs:
|
||||
tag: ${{ github.ref }}
|
||||
|
||||
- name: Linux update AppImage hash
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'ubuntu-20.04'
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os == 'ubuntu-20.04'
|
||||
uses: softprops/action-gh-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -250,6 +259,15 @@ jobs:
|
||||
body: |
|
||||
${{ steps.mac_desktop_build.outputs.package_hash }}
|
||||
|
||||
- name: Cache unix build
|
||||
uses: actions/cache/save@v3
|
||||
if: matrix.os != 'windows-latest'
|
||||
with:
|
||||
path: |
|
||||
${{ matrix.cache_path }}
|
||||
dist-newstyle
|
||||
key: ${{ steps.restore_cache.outputs.cache-primary-key }}
|
||||
|
||||
- name: Unix test
|
||||
if: matrix.os != 'windows-latest'
|
||||
timeout-minutes: 30
|
||||
@@ -259,9 +277,7 @@ jobs:
|
||||
# Unix /
|
||||
|
||||
# / Windows
|
||||
|
||||
# * In powershell multiline commands do not fail if individual commands fail - https://github.community/t/multiline-commands-on-windows-do-not-fail-if-individual-commands-fail/16753
|
||||
# * And GitHub Actions does not support parameterizing shell in a matrix job - https://github.community/t/using-matrix-to-specify-shell-is-it-possible/17065
|
||||
# rm -rf dist-newstyle/src/direct-sq* is here because of the bug in cabal's dependency which prevents second build from finishing
|
||||
|
||||
- name: 'Setup MSYS2'
|
||||
if: matrix.os == 'windows-latest'
|
||||
@@ -283,7 +299,7 @@ jobs:
|
||||
if: matrix.os == 'windows-latest'
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
export PATH=$PATH:/c/ghcup/bin
|
||||
export PATH=$PATH:/c/ghcup/bin:$(echo /c/tools/ghc-*/bin || echo)
|
||||
scripts/desktop/prepare-openssl-windows.sh
|
||||
openssl_windows_style_path=$(echo `pwd`/dist-newstyle/openssl-1.1.1w | sed 's#/\([a-zA-Z]\)#\1:#' | sed 's#/#\\#g')
|
||||
rm cabal.project.local 2>/dev/null || true
|
||||
@@ -325,14 +341,14 @@ jobs:
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest'
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
export PATH=$PATH:/c/ghcup/bin
|
||||
export PATH=$PATH:/c/ghcup/bin:$(echo /c/tools/ghc-*/bin || echo)
|
||||
scripts/desktop/build-lib-windows.sh
|
||||
cd apps/multiplatform
|
||||
./gradlew packageMsi
|
||||
path=$(echo $PWD/release/main/msi/*imple*.msi | sed 's#/\([a-z]\)#\1:#' | sed 's#/#\\#g')
|
||||
echo "package_path=$path" >> $GITHUB_OUTPUT
|
||||
echo "package_hash=$(echo SHA2-512\(${{ matrix.desktop_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT
|
||||
|
||||
|
||||
- name: Windows upload desktop package to release
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest'
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
@@ -352,4 +368,13 @@ jobs:
|
||||
body: |
|
||||
${{ steps.windows_desktop_build.outputs.package_hash }}
|
||||
|
||||
- name: Cache windows build
|
||||
uses: actions/cache/save@v3
|
||||
if: matrix.os == 'windows-latest'
|
||||
with:
|
||||
path: |
|
||||
${{ matrix.cache_path }}
|
||||
dist-newstyle
|
||||
key: ${{ steps.restore_cache.outputs.cache-primary-key }}
|
||||
|
||||
# Windows /
|
||||
|
||||
150
PRIVACY.md
150
PRIVACY.md
@@ -1,134 +1,168 @@
|
||||
# SimpleX Chat Terms & Privacy Policy
|
||||
# SimpleX Chat Privacy Policy and Conditions of Use
|
||||
|
||||
SimpleX Chat is the first communication platform that has no user profile IDs of any kind, not even random numbers. Not only it has no access to your messages (thanks to open-source double-ratchet end-to-end encryption protocol and additional encryption layers), it also has no access to your profile and contacts - we cannot observe your connections graph.
|
||||
SimpleX Chat is the first communication network based on a new protocol stack that builds on the same ideas of complete openness and decentralization as email and web, with the focus on providing security and privacy of communications, and without compromising on usability.
|
||||
|
||||
If you believe that some of the clauses in this document are not aligned with our mission or principles, please raise it with us via [email](chat@simplex.chat) or [chat](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).
|
||||
SimpleX Chat communication protocol is the first protocol that has no user profile IDs of any kind, not even random numbers, cryptographic keys or hashes that identify the users. SimpleX Chat apps allow their users to send messages and files via relay server infrastructure. Relay server owners and providers do not have any access to your messages, thanks to double-ratchet end-to-end encryption algorithm (also known as Signal algorithm - do not confuse with Signal protocols or platform) and additional encryption layers, and they also have no access to your profile and contacts - as they do not provide any user accounts.
|
||||
|
||||
Double ratchet algorithm has such important properties as [forward secrecy](./docs/GLOSSARY.md#forward-secrecy), sender [repudiation](./docs/GLOSSARY.md#) and break-in recovery (also known as [post-compromise security](./docs/GLOSSARY.md#post-compromise-security)).
|
||||
|
||||
If you believe that any part of this document is not aligned with our mission or values, please raise it with us via [email](chat@simplex.chat) or [chat](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).
|
||||
|
||||
## Privacy Policy
|
||||
|
||||
SimpleX Chat Ltd. ("SimpleX Chat") uses the best industry practices for security and encryption to provide secure [end-to-end encrypted](./docs/GLOSSARY.md#end-to-end-encryption) messaging via private connections. This encryption cannot be compromised by the servers via [man-in-the-middle attack](./docs/GLOSSARY.md#man-in-the-middle-attack).
|
||||
SimpleX Chat Ltd uses the best industry practices for security and encryption to provide client and server software for secure [end-to-end encrypted](./docs/GLOSSARY.md#end-to-end-encryption) messaging via private connections. This encryption cannot be compromised by the relays servers, even if they are modified or compromised, via [man-in-the-middle attack](./docs/GLOSSARY.md#man-in-the-middle-attack), unlike most other communication platforms, services and networks.
|
||||
|
||||
SimpleX Chat is built on top of SimpleX messaging and application platform that uses a new message routing protocol allowing to establish private connections without having any kind of addresses that identify its users - we don't use emails, phone numbers, usernames, identity keys or any other user identifiers to pass messages between the users.
|
||||
SimpleX Chat software is built on top of SimpleX messaging and application protocols, based on a new message routing protocol allowing to establish private connections without having any kind of addresses or other identifiers assigned to its users - it does not use emails, phone numbers, usernames, identity keys or any other user profile identifiers to pass messages between the user applications.
|
||||
|
||||
SimpleX Chat security assessment was done in October 2022 by [Trail of Bits](https://www.trailofbits.com/about), and most fixes were released in v4.2.0 – see [the announcement](./blog/20221108-simplex-chat-v4.2-security-audit-new-website.md).
|
||||
SimpleX Chat software is similar in its design approach to email clients and browsers - it allows you to have full control of your data and freely choose the relay server providers, in the same way you choose which website or email provider to use, or use your own relay servers, simply by changing the configuration of the client software. The only current restriction to that is Apple push notifications - at the moment they can only be delivered via the preset servers that we operate, as explained below. We are exploring the solutions to deliver push notifications to iOS devices via other providers or users' own servers.
|
||||
|
||||
### Information you provide
|
||||
While SimpleX Chat Ltd is not a communication service provider, and provide public preset relays "as is", as experimental, without any guarantees of availability or data retention, we are committed to maintain a high level of availability, reliability and security of these preset relays. We will be adding alternative preset infrastructure providers to the software in the future, and you will continue to be able to use any other providers or your own servers.
|
||||
|
||||
We see users and data sovereignty, and device and provider portability as critically important properties for any communication system.
|
||||
|
||||
SimpleX Chat security assessment was done in October 2022 by [Trail of Bits](https://www.trailofbits.com/about), and most fixes were released in v4.2 – see [the announcement](./blog/20221108-simplex-chat-v4.2-security-audit-new-website.md).
|
||||
|
||||
### Your information
|
||||
|
||||
#### User profiles
|
||||
|
||||
We do not store user profiles. The profile you create in the app is local to your device.
|
||||
Servers used by SimpleX Chat apps do not create, store or identify user profiles. The profiles you can create in the app are local to your device, and can be removed at any time via the app.
|
||||
|
||||
When you create a user profile, no records are created on our servers, and we have no access to any part of your profile information, and even to the fact that you created a profile - it is a local record stored only on your device. That means that if you delete the app, and have no backup, you will permanently lose all the data and the private connections you create with other users.
|
||||
When you create the local profile, no records are created on any of the relay servers, and infrastructure providers, whether SimpleX Chat Ltd or any other, have no access to any part of your information, and even to the fact that you created a profile - it is a local record stored only on your device. That means that if you delete the app, and have no backup, you will permanently lose all your data and the private connections you created with other software users.
|
||||
|
||||
You can transfer the profile to another device by creating a backup of the app data and restoring it on the new device, but you cannot use more than one device with the copy of the same profile at the same time - it will disrupt any active conversations on either or both devices, as a security property of end-to-end encryption.
|
||||
|
||||
#### Messages and Files
|
||||
|
||||
SimpleX Chat cannot decrypt or otherwise access the content or even the size of your messages and files you send or receive. Each message is padded to a fixed size of 16kb. Each file is sent in chunks of 256kb, 1mb or 8mb via all or some of the configured file servers. Both messages and files are sent end-to-end encrypted, and the servers do not have technical means to compromise this encryption, because part of the [key exchange](./docs/GLOSSARY.md#key-exchange) happens out-of-band.
|
||||
SimpleX relay servers cannot decrypt or otherwise access the content or even the size of your messages and files you send or receive. Each message is padded to a fixed size of 16kb. Each file is sent in chunks of 64kb, 256kb, 1mb or 8mb via all or some of the configured file relay servers. Both messages and files are sent end-to-end encrypted, and the servers do not have technical means to compromise this encryption, because part of the [key exchange](./docs/GLOSSARY.md#key-exchange) happens out-of-band.
|
||||
|
||||
Your message history is stored only on your own device and the devices of your contacts. While the recipients' devices are offline SimpleX Chat temporarily stores end-to-end encrypted messages on the messaging (SMP) servers that are preset in the app or chosen by the users.
|
||||
Your message history is stored only on your own device and the devices of your contacts. While the recipients' devices are offline, messaging relay servers temporarily store end-to-end encrypted messages – you can configure which relay servers are used to receive the messages from the new contacts, and you can manually change them for the existing contacts too.
|
||||
|
||||
The messages are permanently removed from the preset servers as soon as they are delivered. Undelivered messages are deleted after the time that is configured in the messaging servers you use (21 days for preset messaging servers).
|
||||
You do not have control over which servers are used to send messages to your contacts - they are chosen by them. To send messages your client needs to connect to these servers, therefore the servers chosen by your contacts can observe your IP address. You can use VPN or some overlay network (e.g., Tor) to hide your IP address from the servers chosen by your contacts. In the near future we will add the layer in the messaging protocol that will route sent message via the relays chosen by you as well.
|
||||
|
||||
The files are stored on file (XFTP) servers for the time configured in the file servers you use (48 hours for preset file servers).
|
||||
The messages are permanently removed from the used relay servers as soon as they are delivered, as long as these servers used unmodified published code. Undelivered messages are deleted after the time that is configured in the messaging servers you use (21 days for preset messaging servers).
|
||||
|
||||
If a messaging or file servers are restarted, the encrypted message or the record of the file can be stored in a backup file until it is overwritten by the next restart (usually within 1 week).
|
||||
The files are stored on file relay servers for the time configured in the relay servers you use (48 hours for preset file servers).
|
||||
|
||||
If a messaging servers are restarted, the encrypted message can be stored in a backup file until it is overwritten by the next restart (usually within 1 week for preset relay servers).
|
||||
|
||||
As this software is fully open-source and provided under AGPLv3 license, all infrastructure providers and owners, and the developers of the client and server applications who use the SimpleX Chat source code, are required to publish any changes to this software under the same AGPLv3 license - including any modifications to the provided servers.
|
||||
|
||||
In addition to the AGPLv3 license terms, SimpleX Chat Ltd is committed to the software users that the preset relays that we provide via the apps will always be compiled from the [published open-source code](https://github.com/simplex-chat/simplexmq), without any modifications.
|
||||
|
||||
#### Connections with other users
|
||||
|
||||
When you create a connection with another user, two messaging queues (you can think about them as about mailboxes) are created on chosen messaging servers, that can be the preset servers or the servers that you configured in the app, in case it allows such configuration. SimpleX uses separate queues for direct and response messages, that the client applications prefer to create on two different servers, in case you have more than one server configured in the app, which is the default.
|
||||
When you create a connection with another user, two messaging queues (you can think about them as mailboxes) are created on messaging relay servers (chosen by you and your contact each), that can be the preset servers or the servers that you and your contact configured in the app. SimpleX messaging protocol uses separate queues for direct and response messages, and the apps prefer to create these queues on two different relay servers for increased privacy, in case you have more than one relay server configured in the app, which is the default.
|
||||
|
||||
At the time of updating this document all our client applications allow configuring the servers. Our servers do not store information about which queues are linked to your profile on the device, and they do not collect any information that would allow us to establish that these queues are related to your device or your profile - the access to each queue is authorized by two anonymous unique cryptographic keys, different for each queue, and separate for sender and recipient of the messages.
|
||||
SimpleX relay servers do not store information about which queues are linked to your profile on the device, and they do not collect any information that would allow infrastructure owners and providers to establish that these queues are related to your device or your profile - the access to each queue is authorized by two anonymous unique cryptographic keys, different for each queue, and separate for sender and recipient of the messages.
|
||||
|
||||
#### Connection links privacy
|
||||
|
||||
When you create a connection with another user, the app generates a link/QR code that can be shared with the user to establish the connection via any channel (email, any other messenger, or a video call). This link is safe to share via insecure channels, as long as you can identify the recipient and also trust that this channel did not replace this link (to mitigate the latter risk you can validate the security code via the app).
|
||||
|
||||
While the connection "links" contain SimpleX Chat Ltd domain name `simplex.chat`, this site is never accessed by the app, and is only used for these purposes:
|
||||
- to direct the new users to the app download instructions,
|
||||
- to show connection QR code that can be scanned via the app,
|
||||
- to "namespace" these links,
|
||||
- to open links directly in the installed app when it is clicked outside of the app.
|
||||
|
||||
You can always safely replace the initial part of the link `https://simplex.chat/` either with `simplex:/` (which is a URI scheme provisionally registered with IANA) or with any other domain name where you can self-host the app download instructions and show the connection QR code (but in case it is your domain, it will not open in the app). Also, while the page renders QR code, all the information needed to render it is only available to the browser, as the part of the "link" after `#` symbol is not sent to the website server.
|
||||
|
||||
#### iOS Push Notifications
|
||||
|
||||
When you choose to use instant push notifications in SimpleX iOS app, because the design of push notifications requires storing the device token on notification server, the notifications server can observe how many messaging queues your device has notifications enabled for, and approximately how many messages are sent to each queue.
|
||||
|
||||
Notification server cannot observe the actual addresses of these queues, as a separate address is used to subscribe to the notifications. It also cannot observe who, or even how many contacts, send messages to you, as notifications are delivered to your device end-to-end encrypted by the messaging servers.
|
||||
Preset notification server cannot observe the actual addresses of these queues, as a separate address is used to subscribe to the notifications. It also cannot observe who sends messages to you. Apple push notifications servers can only observe how many notifications are sent to you, but not from how many contacts, or from which messaging relays, as notifications are delivered to your device end-to-end encrypted by one of the preset notification servers - these notifications only contain end-to-end encrypted metadata, not even encrypted message content, and they look completely random to Apple push notification servers.
|
||||
|
||||
It also does not allow to see message content or sizes, as the actual messages are not sent via the notification server, only the fact that the message is available and where it can be received from (the latter information is encrypted, so that the notification server cannot observe it). You can read more about the design of iOS push notifications [here](https://simplex.chat/blog/20220404-simplex-chat-instant-notifications.html#our-ios-approach-has-one-trade-off).
|
||||
You can read more about the design of iOS push notifications [here](https://simplex.chat/blog/20220404-simplex-chat-instant-notifications.html#our-ios-approach-has-one-trade-off).
|
||||
|
||||
#### Another information stored on the servers
|
||||
|
||||
Additional technical information can be stored on our servers, including randomly generated authentication tokens, keys, push tokens, and other material that is necessary to transmit messages. SimpleX Chat limits this additional technical information to the minimum required to operate the Services.
|
||||
Additional technical information can be stored on our servers, including randomly generated authentication tokens, keys, push tokens, and other material that is necessary to transmit messages. SimpleX Chat design limits this additional technical information to the minimum required to operate the software and servers. To prevent server overloading or attacks, the servers can temporarily store data that can link to particular users or devices, including IP addresses, geographic location, or information related to the transport sessions. This information is not stored for the absolute majority of the app users, even for those who use the servers very actively.
|
||||
|
||||
#### SimpleX Directory Service
|
||||
#### SimpleX Directory
|
||||
|
||||
[SimpleX directory service](./docs/DIRECTORY.md) stores: your search requests, the messages and the members profiles in the group. You can connect to SimpleX Directory Service via [this address](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).
|
||||
[SimpleX Directory](./docs/DIRECTORY.md) stores: your search requests, the messages and the members profiles in the registered groups. You can connect to SimpleX Directory via [this address](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).
|
||||
|
||||
#### User Support.
|
||||
#### User Support
|
||||
|
||||
If you contact SimpleX Chat any personal data you may share with us is kept only for the purposes of researching the issue and contacting you about your case. We recommend contacting support [via chat](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), when it is possible.
|
||||
If you contact SimpleX Chat Ltd, any personal data you share with us is kept only for the purposes of researching the issue and contacting you about your case. We recommend contacting support [via chat](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) when it is possible, and avoid sharing any personal information.
|
||||
|
||||
### Information we may share
|
||||
|
||||
We operate our Services using third parties. While we do not share any user data, these third party may access the encrypted user data as it is stored or transmitted via our servers.
|
||||
SimpleX Chat Ltd operates preset relay servers using third parties. While we do not have access and cannot share any user data, these third parties may access the encrypted user messages (but NOT the actual unencrypted message content or size) as it is stored or transmitted via our servers. Hosting providers can also store IP addresses and other transport information as part of their logs.
|
||||
|
||||
We use a third party for email services - if you ask for support via email, your and SimpleX Chat email providers may access these emails according to their privacy policies and terms of service.
|
||||
We use a third party for email services - if you ask for support via email, your and SimpleX Chat Ltd email providers may access these emails according to their privacy policies and terms. When the request is sensitive, we recommend contacting us via SimpleX Chat or using encrypted email using PGP key published at [openpgp.org](https://keys.openpgp.org/search?q=chat%40simplex.chat).
|
||||
|
||||
The cases when SimpleX Chat may need to share the data we temporarily store on the servers:
|
||||
The cases when SimpleX Chat Ltd may share the data temporarily stored on the servers:
|
||||
|
||||
- To meet any applicable law, regulation, legal process or enforceable governmental request.
|
||||
- To enforce applicable Terms, including investigation of potential violations.
|
||||
- To meet any applicable law, or enforceable governmental request or court order.
|
||||
- To enforce applicable terms, including investigation of potential violations.
|
||||
- To detect, prevent, or otherwise address fraud, security, or technical issues.
|
||||
- To protect against harm to the rights, property, or safety of SimpleX Chat, our users, or the public as required or permitted by law.
|
||||
- To protect against harm to the rights, property, or safety of software users, SimpleX Chat Ltd, or the public as required or permitted by law.
|
||||
|
||||
At the time of updating this document, we have never provided or have been requested the access to our servers or any information from our servers by any third parties. If we are ever requested to provide such access or information, we will follow the due legal process.
|
||||
At the time of updating this document, we have never provided or have been requested the access to the preset relay servers or any information from the servers by any third parties. If we are ever requested to provide such access or information, we will follow the due legal process to limit any information shared with the third parties to the minimally required by law.
|
||||
|
||||
### Updates
|
||||
|
||||
We will update this Privacy Policy as needed so that it is current, accurate, and as clear as possible. Your continued use of our Services confirms your acceptance of our updated Privacy Policy.
|
||||
We will update this Privacy Policy as needed so that it is current, accurate, and as clear as possible. Your continued use of our software applications and preset relays infrastructure confirms your acceptance of our updated Privacy Policy.
|
||||
|
||||
Please also read our Terms of Service below.
|
||||
Please also read our Conditions of Use of Software and Infrastructure below.
|
||||
|
||||
If you have questions about our Privacy Policy please contact us via [email](chat@simplex.chat) or [chat](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).
|
||||
|
||||
## Terms of Service
|
||||
## Conditions of Use of Software and Infrastructure
|
||||
|
||||
You accept our Terms of Service ("Terms") by installing or using any of our apps or services ("Services").
|
||||
You accept the Conditions of Use of Software and Infrastructure ("Conditions") by installing or using any of our software or using any of our server infrastructure (collectively referred to as "Applications"), whether preset in the software or not.
|
||||
|
||||
**Minimal age**. You must be at least 13 years old to use our Services. The minimum age to use our Services without parental approval may be higher in your country.
|
||||
**Minimal age**. You must be at least 13 years old to use our Applications. The minimum age to use our Applications without parental approval may be higher in your country.
|
||||
|
||||
**Accessing the servers**. For the efficiency of the network access, the apps access all queues you create on any server via the same network (TCP/IP) connection. Our servers do not collect information about which queues were accessed via the same connection, so we cannot establish which queues belong to the same users. Whoever might observe your network traffic would know which servers you use, and how much data you send, but not to whom it is sent - the data that leaves the servers is always different from the data they receive - there are no identifiers or ciphertext in common. Please refer to our [technical design document](https://github.com/simplex-chat/simplexmq/blob/master/protocol/overview-tjr.md) for more information about our privacy model and known security and privacy risks.
|
||||
**Infrastructure**. Our Infrastructure includes preset messaging and file relay servers, and iOS push notification servers provided by SimpleX Chat Ltd for public use. Our infrastructure does not have any modifications from the [published open-source code](https://github.com/simplex-chat/simplexmq) available under AGPLv3 license. Any infrastructure provider, whether commercial or not, is required by the Affero clause (named after Affero Inc. company that pioneered the community-based Q&A sites in early 2000s) to publish any modifications under the same license. The statements in relation to Infrastructure and relay servers anywhere in this document assume no modifications to the published code, even in the cases when it is not explicitly stated.
|
||||
|
||||
**Privacy of user data**. We do not retain any data we transmit for any longer than necessary to provide the Services. We only collect aggregate statistics across all users, not per user - we do not have information about how many people use SimpleX Chat (we only know an approximate number of app installations and the aggregate traffic through our servers). In any case, we do not and will not sell or in any way monetize user data.
|
||||
**Client applications**. Our client application Software (referred to as "app" or "apps") also has no modifications compared with published open-source code, and any developers of the alternative client apps based on our code are required to publish any modifications under the same AGPLv3 license. Client applications should not include any tracking or analytics code, and do not share any information with SimpleX Chat Ltd or any other third parties. If you ever discover any tracking or analytics code, please report it to us, so we can remove it.
|
||||
|
||||
**Operating our services**. For the purpose of operating our Services, you agree that your end-to-end encrypted messages are transferred via our servers in the United Kingdom, the United States and other countries where we have or use facilities and service providers or partners.
|
||||
**Accessing the infrastructure**. For the efficiency of the network access, the client Software by default accesses all queues your app creates on any relay server within one user profile via the same network (TCP/IP) connection. At the cost of additional traffic this configuration can be changed to use different transport session for each connection. Relay servers do not collect information about which queues were created or accessed via the same connection, so the relay servers cannot establish which queues belong to the same user profile. Whoever might observe your network traffic would know which relay servers you use, and how much data you send, but not to whom it is sent - the data that leaves the servers is always different from the data they receive - there are no identifiers or ciphertext in common, even inside TLS encryption layer. Please refer to our [technical design document](https://github.com/simplex-chat/simplexmq/blob/master/protocol/overview-tjr.md) for more information about our privacy model and known security and privacy risks.
|
||||
|
||||
**Software**. You agree to downloading and installing updates to our Services when they are available; they would only be automatic if you configure your devices in this way.
|
||||
**Privacy of user data**. Servers do not retain any data we transmit for any longer than necessary to deliver the messages between apps. SimpleX Chat Ltd collects aggregate statistics across all its servers, as supported by published code and can be enabled by any infrastructure provider, but not any statistics per-user, or per geographic location, or per IP address, or per transport session. We do not have information about how many people use SimpleX Chat applications, we only know an approximate number of app installations and the aggregate traffic through the preset servers. In any case, we do not and will not sell or in any way monetize user data. Our future business model assumes charging for some optional Software features instead, in a transparent and fair way.
|
||||
|
||||
**Traffic and device costs**. You are solely responsible for the traffic and device costs on which you use our Services, and any associated taxes.
|
||||
**Operating our Infrastructure**. For the purpose of using our Software, if you continue using preset servers, you agree that your end-to-end encrypted messages are transferred via the preset servers in any countries where we have or use facilities and service providers or partners. The information about geographic location of the servers will be made available in the apps in the near future.
|
||||
|
||||
**Legal and acceptable usage**. You agree to use our Services only for legal and acceptable purposes. You will not use (or assist others in using) our Services in ways that: 1) violate or infringe the rights of SimpleX Chat, our users, or others, including privacy, publicity, intellectual property, or other proprietary rights; 2) involve sending illegal or impermissible communications, e.g. spam.
|
||||
**Software**. You agree to downloading and installing updates to our Applications when they are available; they would only be automatic if you configure your devices in this way.
|
||||
|
||||
**Damage to SimpleX Chat**. You must not (or assist others to) access, use, modify, distribute, transfer, or exploit our Services in unauthorized manners, or in ways that harm SimpleX Chat, our Services, or systems. For example, you must not 1) access our Services or systems without authorization, other than by using the apps; 2) disrupt the integrity or performance of our Services; 3) collect information about our users in any manner; or 4) sell, rent, or charge for our Services.
|
||||
**Traffic and device costs**. You are solely responsible for the traffic and device costs that you incur while using our Applications, and any associated taxes.
|
||||
|
||||
**Keeping your data secure**. SimpleX Chat is the first messaging platform that is 100% private by design - we neither have ability to access your messages, nor we have information about who you communicate with. That means that you are solely responsible for keeping your device and your user profile safe and secure. If you lose your phone or remove the app, you will not be able to recover the lost data, unless you made a back up.
|
||||
**Legal and acceptable usage**. You agree to use our Applications only for legal and acceptable purposes. You will not use (or assist others in using) our Applications in ways that: 1) violate or infringe the rights of Software users, SimpleX Chat Ltd, or others, including privacy, publicity, intellectual property, or other proprietary rights; 2) involve sending illegal or impermissible communications, e.g. spam. While we cannot access content or identify messages or groups, in some cases the links to the illegal or impermissible communications available via our Applications can be shared publicly on social media or websites. We reserve the right to remove such links from the preset servers and disrupt the conversations that send illegal content via our servers, whether they were reported by the users or discovered by our team.
|
||||
|
||||
**Storing the messages on the device**. The messages are stored in the encrypted database on your device. Whether and how database passphrase is stored is determined by the configuration of the application you use. Legacy databases created prior to 2023 or in CLI (terminal) app may remain unencrypted, and it will be indicated in the app. In this case, if you make a backup of the app data and store it unencrypted, the backup provider may be able to access the messages. Please note, that the beta version of desktop app currently stores the database passphrase in the configuration file in plaintext, so you may need to remove passphrase from the device via the app configuration.
|
||||
**Damage to SimpleX Chat Ltd**. You must not (or assist others to) access, use, modify, distribute, transfer, or exploit our Applications in unauthorized manners, or in ways that harm Software users, SimpleX Chat Ltd, our Infrastructure, or any other systems. For example, you must not 1) access our Infrastructure or systems without authorization, in any way other than by using the Software; 2) disrupt the integrity or performance of our Infrastructure; 3) collect information about our users in any manner; or 4) sell, rent, or charge for our Infrastructure. This does not prohibit you from providing your own Infrastructure to others, whether free or for a fee, as long as you do not violate these Conditions and AGPLv3 license, including the requirement to publish any modifications of the relay server software.
|
||||
|
||||
**Storing the files on the device**. The files are stored on your device unencrypted. If you make a backup of the app data and store it unencrypted, the backup provider will be able to access the files.
|
||||
**Keeping your data secure**. SimpleX Chat is the first communication software that aims to be 100% private by design - server software neither has the ability to access your messages, nor it has information about who you communicate with. That means that you are solely responsible for keeping your device, your user profile and any data safe and secure. If you lose your phone or remove the Software from the device, you will not be able to recover the lost data, unless you made a back up. To protect the data you need to make regular backups, as using old backups may disrupt your communication with some of the contacts.
|
||||
|
||||
**No Access to Emergency Services**. Our Services do not provide access to emergency service providers like the police, fire department, hospitals, or other public safety organizations. Make sure you can contact emergency service providers through a mobile, fixed-line telephone, or other service.
|
||||
**Storing the messages on the device**. The messages are stored in the encrypted database on your device. Whether and how database passphrase is stored is determined by the configuration of the Software you use. The databases created prior to 2023 or in CLI (terminal) app may remain unencrypted, and it will be indicated in the app interface. In this case, if you make a backup of the data and store it unencrypted, the backup provider may be able to access the messages. Please note, that the desktop apps can be configured to store the database passphrase in the configuration file in plaintext, and unless you set the passphrase when first running the app, a random passphrase will be used and stored on the device. You can remove it from the device via the app settings.
|
||||
|
||||
**Third-party services**. Our Services may allow you to access, use, or interact with third-party websites, apps, content, and other products and services. When you use third-party services, their terms and privacy policies govern your use of those services.
|
||||
**Storing the files on the device**. The files currently sent and received in the apps by default (except CLI app) are stored on your device encrypted using unique keys, different for each file, that are stored in the database. Once the message that the file was attached to is removed, even if the copy of the encrypted file is retained, it should be impossible to recover the key allowing to decrypt the file. This local file encryption may affect app performance, and it can be disabled via the app settings. This change will only affect the new files. If you later re-enable the encryption, it will also affect only the new files. If you make a backup of the app data and store it unencrypted, the backup provider will be able to access any unencrypted files. In any case, irrespective of the storage setting, the files are always sent by all apps end-to-end encrypted.
|
||||
|
||||
**Your Rights**. You own the messages and the information you transmit through our Services. Your recipients are able to retain the messages you receive from you; there is no technical ability to delete data from their devices. While there are various app features that allow deleting messages from the recipients' devices, such as _disappearing messages_ and _full message deletion_, their functioning on your recipients' devices cannot be guaranteed or enforced, as the device may be offline or have a modified version of the app.
|
||||
**No Access to Emergency Services**. Our Applications do not provide access to emergency service providers like the police, fire department, hospitals, or other public safety organizations. Make sure you can contact emergency service providers through a mobile, fixed-line telephone, or other service.
|
||||
|
||||
**License**. SimpleX Chat grants you a limited, revocable, non-exclusive, and non-transferable license to use our Services in accordance with these Terms. The source-code of services is available and can be used under [AGPL v3 license](https://github.com/simplex-chat/simplex-chat/blob/stable/LICENSE)
|
||||
**Third-party services**. Our Applications may allow you to access, use, or interact with our or third-party websites, apps, content, and other products and services. When you use third-party services, their terms and privacy policies govern your use of those services.
|
||||
|
||||
**SimpleX Chat Rights**. We own all copyrights, trademarks, domains, logos, trade secrets, and other intellectual property rights associated with our Services. You may not use our copyrights, trademarks, domains, logos, and other intellectual property rights unless you have our written permission, and unless under an open-source license distributed together with the source code. To report copyright, trademark, or other intellectual property infringement, please contact chat@simplex.chat.
|
||||
**Your Rights**. You own the messages and the information you transmit through our Applications. Your recipients are able to retain the messages they receive from you; there is no technical ability to delete data from their devices. While there are various app features that allow deleting messages from the recipients' devices, such as _disappearing messages_ and _full message deletion_, their functioning on your recipients' devices cannot be guaranteed or enforced, as the device may be offline or have a modified version of the Software. At the same time, repudiation property of the end-to-end encryption algorithm allows you to plausibly deny having sent the message, like you can deny what you said in a private face-to-face conversation, as the recipient cannot provide any proof to the third parties, by design.
|
||||
|
||||
**Disclaimers**. YOU USE OUR SERVICES AT YOUR OWN RISK AND SUBJECT TO THE FOLLOWING DISCLAIMERS. WE PROVIDE OUR SERVICES ON AN “AS IS” BASIS WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, NON-INFRINGEMENT, AND FREEDOM FROM COMPUTER VIRUS OR OTHER HARMFUL CODE. SIMPLEX DOES NOT WARRANT THAT ANY INFORMATION PROVIDED BY US IS ACCURATE, COMPLETE, OR USEFUL, THAT OUR SERVICES WILL BE OPERATIONAL, ERROR-FREE, SECURE, OR SAFE, OR THAT OUR SERVICES WILL FUNCTION WITHOUT DISRUPTIONS, DELAYS, OR IMPERFECTIONS. WE DO NOT CONTROL, AND ARE NOT RESPONSIBLE FOR, CONTROLLING HOW OR WHEN OUR USERS USE OUR SERVICES. WE ARE NOT RESPONSIBLE FOR THE ACTIONS OR INFORMATION (INCLUDING CONTENT) OF OUR USERS OR OTHER THIRD PARTIES. YOU RELEASE US, AFFILIATES, DIRECTORS, OFFICERS, EMPLOYEES, PARTNERS, AND AGENTS ("SIMPLEX PARTIES") FROM ANY CLAIM, COMPLAINT, CAUSE OF ACTION, CONTROVERSY, OR DISPUTE (TOGETHER, "CLAIM") AND DAMAGES, KNOWN AND UNKNOWN, RELATING TO, ARISING OUT OF, OR IN ANY WAY CONNECTED WITH ANY SUCH CLAIM YOU HAVE AGAINST ANY THIRD PARTIES.
|
||||
**License**. SimpleX Chat Ltd grants you a limited, revocable, non-exclusive, and non-transferable license to use our Applications in accordance with these Conditions. The source-code of Applications is available and can be used under [AGPL v3 license](https://github.com/simplex-chat/simplex-chat/blob/stable/LICENSE).
|
||||
|
||||
**Limitation of liability**. THE SIMPLEX PARTIES WILL NOT BE LIABLE TO YOU FOR ANY LOST PROFITS OR CONSEQUENTIAL, SPECIAL, PUNITIVE, INDIRECT, OR INCIDENTAL DAMAGES RELATING TO, ARISING OUT OF, OR IN ANY WAY IN CONNECTION WITH OUR TERMS, US, OR OUR SERVICES, EVEN IF THE SIMPLEX PARTIES HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. OUR AGGREGATE LIABILITY RELATING TO, ARISING OUT OF, OR IN ANY WAY IN CONNECTION WITH OUR TERMS, US, OR OUR SERVICES WILL NOT EXCEED ONE DOLLAR ($1). THE FOREGOING DISCLAIMER OF CERTAIN DAMAGES AND LIMITATION OF LIABILITY WILL APPLY TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW. THE LAWS OF SOME JURISDICTIONS MAY NOT ALLOW THE EXCLUSION OR LIMITATION OF CERTAIN DAMAGES, SO SOME OR ALL OF THE EXCLUSIONS AND LIMITATIONS SET FORTH ABOVE MAY NOT APPLY TO YOU. NOTWITHSTANDING ANYTHING TO THE CONTRARY IN OUR TERMS, IN SUCH CASES, THE LIABILITY OF THE SIMPLEX PARTIES WILL BE LIMITED TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
**SimpleX Chat Ltd Rights**. We own all copyrights, trademarks, domains, logos, trade secrets, and other intellectual property rights associated with our Applications. You may not use our copyrights, trademarks, domains, logos, and other intellectual property rights unless you have our written permission, and unless under an open-source license distributed together with the source code. To report copyright, trademark, or other intellectual property infringement, please contact chat@simplex.chat.
|
||||
|
||||
**Availability**. Our Services may be interrupted, including for maintenance, upgrades, or network or equipment failures. We may discontinue some or all of our Services, including certain features and the support for certain devices and platforms, at any time.
|
||||
**Disclaimers**. YOU USE OUR APPLICATIONS AT YOUR OWN RISK AND SUBJECT TO THE FOLLOWING DISCLAIMERS. WE PROVIDE OUR APPLICATIONS ON AN “AS IS” BASIS WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, NON-INFRINGEMENT, AND FREEDOM FROM COMPUTER VIRUS OR OTHER HARMFUL CODE. SIMPLEX CHAT LTD DOES NOT WARRANT THAT ANY INFORMATION PROVIDED BY US IS ACCURATE, COMPLETE, OR USEFUL, THAT OUR APPLICATIONS WILL BE OPERATIONAL, ERROR-FREE, SECURE, OR SAFE, OR THAT OUR APPLICATIONS WILL FUNCTION WITHOUT DISRUPTIONS, DELAYS, OR IMPERFECTIONS. WE DO NOT CONTROL, AND ARE NOT RESPONSIBLE FOR, CONTROLLING HOW OR WHEN OUR USERS USE OUR APPLICATIONS. WE ARE NOT RESPONSIBLE FOR THE ACTIONS OR INFORMATION (INCLUDING CONTENT) OF OUR USERS OR OTHER THIRD PARTIES. YOU RELEASE US, AFFILIATES, DIRECTORS, OFFICERS, EMPLOYEES, PARTNERS, AND AGENTS ("SIMPLEX PARTIES") FROM ANY CLAIM, COMPLAINT, CAUSE OF ACTION, CONTROVERSY, OR DISPUTE (TOGETHER, "CLAIM") AND DAMAGES, KNOWN AND UNKNOWN, RELATING TO, ARISING OUT OF, OR IN ANY WAY CONNECTED WITH ANY SUCH CLAIM YOU HAVE AGAINST ANY THIRD PARTIES.
|
||||
|
||||
**Resolving disputes**. You agree to resolve any Claim you have with us relating to or arising from our Terms, us, or our Services in the courts of England and Wales. You also agree to submit to the personal jurisdiction of such courts for the purpose of resolving all such disputes. The laws of England govern our Terms, as well as any disputes, whether in court or arbitration, which might arise between SimpleX Chat and you, without regard to conflict of law provisions.
|
||||
**Limitation of liability**. THE SIMPLEX PARTIES WILL NOT BE LIABLE TO YOU FOR ANY LOST PROFITS OR CONSEQUENTIAL, SPECIAL, PUNITIVE, INDIRECT, OR INCIDENTAL DAMAGES RELATING TO, ARISING OUT OF, OR IN ANY WAY IN CONNECTION WITH OUR CONDITIONS, US, OR OUR APPLICATIONS, EVEN IF THE SIMPLEX PARTIES HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. OUR AGGREGATE LIABILITY RELATING TO, ARISING OUT OF, OR IN ANY WAY IN CONNECTION WITH OUR CONDITIONS, US, OR OUR APPLICATIONS WILL NOT EXCEED ONE DOLLAR ($1). THE FOREGOING DISCLAIMER OF CERTAIN DAMAGES AND LIMITATION OF LIABILITY WILL APPLY TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW. THE LAWS OF SOME JURISDICTIONS MAY NOT ALLOW THE EXCLUSION OR LIMITATION OF CERTAIN DAMAGES, SO SOME OR ALL OF THE EXCLUSIONS AND LIMITATIONS SET FORTH ABOVE MAY NOT APPLY TO YOU. NOTWITHSTANDING ANYTHING TO THE CONTRARY IN OUR CONDITIONS, IN SUCH CASES, THE LIABILITY OF THE SIMPLEX PARTIES WILL BE LIMITED TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
|
||||
**Changes to the terms**. SimpleX Chat may update the Terms from time to time. Your continued use of our Services confirms your acceptance of our updated Terms and supersedes any prior Terms. You will comply with all applicable export control and trade sanctions laws. Our Terms cover the entire agreement between you and SimpleX Chat regarding our Services. If you do not agree with our Terms, you should stop using our Services.
|
||||
**Availability**. Our Applications may be disrupted, including for maintenance, upgrades, or network or equipment failures. We may discontinue some or all of our Applications, including certain features and the support for certain devices and platforms, at any time.
|
||||
|
||||
**Enforcing the terms**. If we fail to enforce any of our Terms, that does not mean we waive the right to enforce them. If any provision of the Terms is deemed unlawful, void, or unenforceable, that provision shall be deemed severable from our Terms and shall not affect the enforceability of the remaining provisions. Our Services are not intended for distribution to or use in any country where such distribution or use would violate local law or would subject us to any regulations in another country. We reserve the right to limit our Services in any country. If you have specific questions about these Terms, please contact us at chat@simplex.chat.
|
||||
**Resolving disputes**. You agree to resolve any Claim you have with us relating to or arising from our Conditions, us, or our Applications in the courts of England and Wales. You also agree to submit to the personal jurisdiction of such courts for the purpose of resolving all such disputes. The laws of England govern our Conditions, as well as any disputes, whether in court or arbitration, which might arise between SimpleX Chat Ltd and you, without regard to conflict of law provisions.
|
||||
|
||||
**Ending these Terms**. You may end these Terms with SimpleX Chat at any time by deleting SimpleX Chat app(s) from your device and discontinuing use of our Services. The provisions related to Licenses, Disclaimers, Limitation of Liability, Resolving dispute, Availability, Changes to the terms, Enforcing the terms, and Ending these Terms will survive termination of your relationship with SimpleX Chat.
|
||||
**Changes to the conditions**. SimpleX Chat Ltd may update the Conditions from time to time. Your continued use of our Applications confirms your acceptance of our updated Conditions and supersedes any prior Conditions. You will comply with all applicable export control and trade sanctions laws. Our Conditions cover the entire agreement between you and SimpleX Chat Ltd regarding our Applications. If you do not agree with our Conditions, you should stop using our Applications.
|
||||
|
||||
Updated August 17, 2023
|
||||
**Enforcing the conditions**. If we fail to enforce any of our Conditions, that does not mean we waive the right to enforce them. If any provision of the Conditions is deemed unlawful, void, or unenforceable, that provision shall be deemed severable from our Conditions and shall not affect the enforceability of the remaining provisions. Our Applications are not intended for distribution to or use in any country where such distribution or use would violate local law or would subject us to any regulations in another country. We reserve the right to limit our Applications in any country. If you have specific questions about these Conditions, please contact us at chat@simplex.chat.
|
||||
|
||||
**Ending these conditions**. You may end these Conditions with SimpleX Chat Ltd at any time by deleting our Applications from your devices and discontinuing use of our Infrastructure. The provisions related to Licenses, Disclaimers, Limitation of Liability, Resolving dispute, Availability, Changes to the conditions, Enforcing the conditions, and Ending these conditions will survive termination of your relationship with SimpleX Chat Ltd.
|
||||
|
||||
Updated February 24, 2024
|
||||
|
||||
13
README.md
13
README.md
@@ -72,7 +72,7 @@ You must:
|
||||
|
||||
Messages not following these rules will be deleted, the right to send messages may be revoked, and the access to the new members to the group may be temporarily restricted, to prevent re-joining under a different name - our imperfect group moderation does not have a better solution at the moment.
|
||||
|
||||
You can join an English-speaking users group if you want to ask any questions: [#SimpleX-Group-4](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2Fw2GlucRXtRVgYnbt_9ZP-kmt76DekxxS%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEA0tJhTyMGUxznwmjb7aT24P1I1Wry_iURTuhOFlMb1Eo%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22WoPxjFqGEDlVazECOSi2dg%3D%3D%22%7D)
|
||||
You can join an English-speaking users group if you want to ask any questions: [#SimpleX users group](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2Fos8FftfoV8zjb2T89fUEjJtF7y64p5av%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAQqMgh0fw2lPhjn3PDIEfAKA_E0-gf8Hr8zzhYnDivRs%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22lBPiveK2mjfUH43SN77R0w%3D%3D%22%7D)
|
||||
|
||||
There is also a group [#simplex-devs](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2F6eHqy7uAbZPOcA6qBtrQgQquVlt4Ll91%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAqV_pg3FF00L98aCXp4D3bOs4Sxv_UmSd-gb0juVoQVs%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22XonlixcHBIb2ijCehbZoiw%3D%3D%22%7D) for developers who build on SimpleX platform:
|
||||
|
||||
@@ -127,6 +127,7 @@ Join our translators to help SimpleX grow!
|
||||
|🇫🇮 fi|Suomi | |[](https://hosted.weblate.org/projects/simplex-chat/android/fi/)<br>[](https://hosted.weblate.org/projects/simplex-chat/ios/fi/)|[](https://hosted.weblate.org/projects/simplex-chat/website/fi/)||
|
||||
|🇫🇷 fr|Français |[ishi_sama](https://github.com/ishi-sama)|[](https://hosted.weblate.org/projects/simplex-chat/android/fr/)<br>[](https://hosted.weblate.org/projects/simplex-chat/ios/fr/)|[](https://hosted.weblate.org/projects/simplex-chat/website/fr/)|[✓](https://github.com/simplex-chat/simplex-chat/tree/master/docs/lang/fr)|
|
||||
|🇮🇱 he|עִברִית | |[](https://hosted.weblate.org/projects/simplex-chat/android/he/)<br>-|||
|
||||
|🇭🇺 hu|Magyar | |[](https://hosted.weblate.org/projects/simplex-chat/android/hu/)<br>-|||
|
||||
|🇮🇹 it|Italiano |[unbranched](https://github.com/unbranched)|[](https://hosted.weblate.org/projects/simplex-chat/android/it/)<br>[](https://hosted.weblate.org/projects/simplex-chat/ios/it/)|[](https://hosted.weblate.org/projects/simplex-chat/website/it/)||
|
||||
|🇯🇵 ja|日本語 | |[](https://hosted.weblate.org/projects/simplex-chat/android/ja/)<br>[](https://hosted.weblate.org/projects/simplex-chat/ios/ja/)|[](https://hosted.weblate.org/projects/simplex-chat/website/ja/)||
|
||||
|🇳🇱 nl|Nederlands|[mika-nl](https://github.com/mika-nl)|[](https://hosted.weblate.org/projects/simplex-chat/android/nl/)<br>[](https://hosted.weblate.org/projects/simplex-chat/ios/nl/)|[](https://hosted.weblate.org/projects/simplex-chat/website/nl/)||
|
||||
@@ -134,6 +135,7 @@ Join our translators to help SimpleX grow!
|
||||
|🇧🇷 pt-BR|Português||[](https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/)<br>-|[](https://hosted.weblate.org/projects/simplex-chat/website/pt_BR/)||
|
||||
|🇷🇺 ru|Русский ||[](https://hosted.weblate.org/projects/simplex-chat/android/ru/)<br>[](https://hosted.weblate.org/projects/simplex-chat/ios/ru/)|||
|
||||
|🇹🇭 th|ภาษาไทย |[titapa-punpun](https://github.com/titapa-punpun)|[](https://hosted.weblate.org/projects/simplex-chat/android/th/)<br>[](https://hosted.weblate.org/projects/simplex-chat/ios/th/)|||
|
||||
|🇹🇷 tr|Türkçe | |[](https://hosted.weblate.org/projects/simplex-chat/android/tr/)<br>[](https://hosted.weblate.org/projects/simplex-chat/ios/tr/)|||
|
||||
|🇺🇦 uk|Українська| |[](https://hosted.weblate.org/projects/simplex-chat/android/uk/)<br>[](https://hosted.weblate.org/projects/simplex-chat/ios/uk/)|[](https://hosted.weblate.org/projects/simplex-chat/website/uk/)||
|
||||
|🇨🇳 zh-CHS|简体中文|[sith-on-mars](https://github.com/sith-on-mars)<br><br>[Float-hu](https://github.com/Float-hu)|[](https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/)<br>[](https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hans/)<br> |<br><br>[](https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/)||
|
||||
|
||||
@@ -232,6 +234,8 @@ You can use SimpleX with your own servers and still communicate with people usin
|
||||
|
||||
Recent and important updates:
|
||||
|
||||
[Jan 24, 2024. SimpleX Chat: free infrastructure from Linode, v5.5 released with private notes, group history and a simpler UX to connect.](./blog/20240124-simplex-chat-infrastructure-costs-v5-5-simplex-ux-private-notes-group-history.md)
|
||||
|
||||
[Nov 25, 2023. SimpleX Chat v5.4 released: link mobile and desktop apps via quantum resistant protocol, and much better groups](./blog/20231125-simplex-chat-v5-4-link-mobile-desktop-quantum-resistant-better-groups.md).
|
||||
|
||||
[Sep 25, 2023. SimpleX Chat v5.3 released: desktop app, local file encryption, improved groups and directory service](./blog/20230925-simplex-chat-v5-3-desktop-app-local-file-encryption-directory-service.md).
|
||||
@@ -297,7 +301,7 @@ What is already implemented:
|
||||
11. Transport isolation - different TCP connections and Tor circuits are used for traffic of different user profiles, optionally - for different contacts and group member connections.
|
||||
12. Manual messaging queue rotations to move conversation to another SMP relay.
|
||||
13. Sending end-to-end encrypted files using [XFTP protocol](https://simplex.chat/blog/20230301-simplex-file-transfer-protocol.html).
|
||||
14. Local files encryption, except videos (to be added later).
|
||||
14. Local files encryption.
|
||||
|
||||
We plan to add:
|
||||
|
||||
@@ -369,12 +373,13 @@ Please also join [#simplex-devs](https://simplex.chat/contact#/?v=1-2&smp=smp%3A
|
||||
- ✅ Desktop client.
|
||||
- ✅ Encryption of local files stored in the app.
|
||||
- ✅ Using mobile profiles from the desktop app.
|
||||
- ✅ Private notes.
|
||||
- ✅ Improve sending videos (including encryption of locally stored videos).
|
||||
- 🏗 Improve experience for the new users.
|
||||
- 🏗 Post-quantum resistant key exchange in double ratchet protocol.
|
||||
- 🏗 Large groups, communities and public channels.
|
||||
- Message delivery relay for senders (to conceal IP address from the recipients' servers and to reduce the traffic).
|
||||
- 🏗 Message delivery relay for senders (to conceal IP address from the recipients' servers and to reduce the traffic).
|
||||
- Privacy & security slider - a simple way to set all settings at once.
|
||||
- Improve sending videos (including encryption of locally stored videos).
|
||||
- SMP queue redundancy and rotation (manual is supported).
|
||||
- Include optional message into connection request sent via contact address.
|
||||
- Improved navigation and search in the conversation (expand and scroll to quoted message, scroll to search results, etc.).
|
||||
|
||||
@@ -15,6 +15,8 @@ class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
logger.debug("AppDelegate: didFinishLaunchingWithOptions")
|
||||
application.registerForRemoteNotifications()
|
||||
if #available(iOS 17.0, *) { trackKeyboard() }
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(pasteboardChanged), name: UIPasteboard.changedNotification, object: nil)
|
||||
removePasswordsIfReinstalled()
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -36,6 +38,10 @@ class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
ChatModel.shared.keyboardHeight = 0
|
||||
}
|
||||
|
||||
@objc func pasteboardChanged() {
|
||||
ChatModel.shared.pasteboardHasStrings = UIPasteboard.general.hasStrings
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
||||
let token = deviceToken.map { String(format: "%02hhx", $0) }.joined()
|
||||
logger.debug("AppDelegate: didRegisterForRemoteNotificationsWithDeviceToken \(token)")
|
||||
@@ -122,6 +128,17 @@ class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
BGManager.shared.receiveMessages(complete)
|
||||
}
|
||||
|
||||
private func removePasswordsIfReinstalled() {
|
||||
// check for database existence is needed because app password and self destruct password will be saved and restored
|
||||
// by iOS when a user deletes the app and installs it again. In this case database will be deleted but passwords are not.
|
||||
// This check ensures that the user will not stack on "Opening app..." screen
|
||||
if (kcAppPassword.get() != nil || kcSelfDestructPassword.get() != nil) && !UserDefaults.standard.bool(forKey: DEFAULT_PERFORM_LA) && !(hasDatabase() || hasLegacyDatabase()) {
|
||||
_ = kcAppPassword.remove()
|
||||
_ = kcSelfDestructPassword.remove()
|
||||
_ = kcDatabasePassword.remove()
|
||||
}
|
||||
}
|
||||
|
||||
static func keepScreenOn(_ on: Bool) {
|
||||
UIApplication.shared.isIdleTimerDisabled = on
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ struct ContentView: View {
|
||||
@State private var showWhatsNew = false
|
||||
@State private var showChooseLAMode = false
|
||||
@State private var showSetPasscode = false
|
||||
@State private var waitingForOrPassedAuth = true
|
||||
@State private var chatListActionSheet: ChatListActionSheet? = nil
|
||||
|
||||
private enum ChatListActionSheet: Identifiable {
|
||||
@@ -61,6 +62,10 @@ struct ContentView: View {
|
||||
}
|
||||
if !showSettings, let la = chatModel.laRequest {
|
||||
LocalAuthView(authRequest: la)
|
||||
.onDisappear {
|
||||
// this flag is separate from accessAuthenticated to show initializationView while we wait for authentication
|
||||
waitingForOrPassedAuth = accessAuthenticated
|
||||
}
|
||||
} else if showSetPasscode {
|
||||
SetAppPasscodeView {
|
||||
chatModel.contentViewAccessAuthenticated = true
|
||||
@@ -73,8 +78,7 @@ struct ContentView: View {
|
||||
showSetPasscode = false
|
||||
alertManager.showAlert(laPasscodeNotSetAlert())
|
||||
}
|
||||
}
|
||||
if chatModel.chatDbStatus == nil {
|
||||
} else if chatModel.chatDbStatus == nil && AppChatState.shared.value != .stopped && waitingForOrPassedAuth {
|
||||
initializationView()
|
||||
}
|
||||
}
|
||||
@@ -182,7 +186,7 @@ struct ContentView: View {
|
||||
.onAppear {
|
||||
requestNtfAuthorization()
|
||||
// Local Authentication notice is to be shown on next start after onboarding is complete
|
||||
if (!prefLANoticeShown && prefShowLANotice && !chatModel.chats.isEmpty) {
|
||||
if (!prefLANoticeShown && prefShowLANotice && chatModel.chats.count > 2) {
|
||||
prefLANoticeShown = true
|
||||
alertManager.showAlert(laNoticeAlert())
|
||||
} else if !chatModel.showCallView && CallController.shared.activeCallInvitation == nil {
|
||||
|
||||
@@ -54,11 +54,13 @@ final class ChatModel: ObservableObject {
|
||||
@Published var chatDbChanged = false
|
||||
@Published var chatDbEncrypted: Bool?
|
||||
@Published var chatDbStatus: DBMigrationResult?
|
||||
@Published var ctrlInitInProgress: Bool = false
|
||||
// local authentication
|
||||
@Published var contentViewAccessAuthenticated: Bool = false
|
||||
@Published var laRequest: LocalAuthRequest?
|
||||
// list of chat "previews"
|
||||
@Published var chats: [Chat] = []
|
||||
@Published var deletedChats: Set<String> = []
|
||||
// map of connections network statuses, key is agent connection id
|
||||
@Published var networkStatuses: Dictionary<String, NetworkStatus> = [:]
|
||||
// current chat
|
||||
@@ -89,14 +91,15 @@ final class ChatModel: ObservableObject {
|
||||
@Published var showCallView = false
|
||||
// remote desktop
|
||||
@Published var remoteCtrlSession: RemoteCtrlSession?
|
||||
// currently showing QR code
|
||||
@Published var connReqInv: String?
|
||||
// currently showing invitation
|
||||
@Published var showingInvitation: ShowingInvitation?
|
||||
// audio recording and playback
|
||||
@Published var stopPreviousRecPlay: URL? = nil // coordinates currently playing source
|
||||
@Published var draft: ComposeState?
|
||||
@Published var draftChatId: String?
|
||||
// tracks keyboard height via subscription in AppDelegate
|
||||
@Published var keyboardHeight: CGFloat = 0
|
||||
@Published var pasteboardHasStrings: Bool = UIPasteboard.general.hasStrings
|
||||
|
||||
var messageDelivery: Dictionary<Int64, () -> Void> = [:]
|
||||
|
||||
@@ -136,7 +139,7 @@ final class ChatModel: ObservableObject {
|
||||
}
|
||||
|
||||
func removeUser(_ user: User) {
|
||||
if let i = getUserIndex(user), users[i].user.userId != currentUser?.userId {
|
||||
if let i = getUserIndex(user) {
|
||||
users.remove(at: i)
|
||||
}
|
||||
}
|
||||
@@ -620,14 +623,16 @@ final class ChatModel: ObservableObject {
|
||||
}
|
||||
|
||||
func dismissConnReqView(_ id: String) {
|
||||
if let connReqInv = connReqInv,
|
||||
let c = getChat(id),
|
||||
case let .contactConnection(contactConnection) = c.chatInfo,
|
||||
connReqInv == contactConnection.connReqInv {
|
||||
if id == showingInvitation?.connId {
|
||||
markShowingInvitationUsed()
|
||||
dismissAllSheets()
|
||||
}
|
||||
}
|
||||
|
||||
func markShowingInvitationUsed() {
|
||||
showingInvitation?.connChatUsed = true
|
||||
}
|
||||
|
||||
func removeChat(_ id: String) {
|
||||
withAnimation {
|
||||
chats.removeAll(where: { $0.id == id })
|
||||
@@ -704,6 +709,11 @@ final class ChatModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
struct ShowingInvitation {
|
||||
var connId: String
|
||||
var connChatUsed: Bool
|
||||
}
|
||||
|
||||
struct NTFContactRequest {
|
||||
var incognito: Bool
|
||||
var chatId: String
|
||||
@@ -746,6 +756,8 @@ final class Chat: ObservableObject, Identifiable {
|
||||
case let .group(groupInfo):
|
||||
let m = groupInfo.membership
|
||||
return m.memberActive && m.memberRole >= .member
|
||||
case .local:
|
||||
return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +158,8 @@ func imageHasAlpha(_ img: UIImage) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func saveFileFromURL(_ url: URL, encrypted: Bool) -> CryptoFile? {
|
||||
func saveFileFromURL(_ url: URL) -> CryptoFile? {
|
||||
let encrypted = privacyEncryptLocalFilesGroupDefault.get()
|
||||
let savedFile: CryptoFile?
|
||||
if url.startAccessingSecurityScopedResource() {
|
||||
do {
|
||||
@@ -185,10 +186,19 @@ func saveFileFromURL(_ url: URL, encrypted: Bool) -> CryptoFile? {
|
||||
|
||||
func moveTempFileFromURL(_ url: URL) -> CryptoFile? {
|
||||
do {
|
||||
let encrypted = privacyEncryptLocalFilesGroupDefault.get()
|
||||
let fileName = uniqueCombine(url.lastPathComponent)
|
||||
try FileManager.default.moveItem(at: url, to: getAppFilePath(fileName))
|
||||
let savedFile: CryptoFile?
|
||||
if encrypted {
|
||||
let cfArgs = try encryptCryptoFile(fromPath: url.path, toPath: getAppFilePath(fileName).path)
|
||||
try FileManager.default.removeItem(atPath: url.path)
|
||||
savedFile = CryptoFile(filePath: fileName, cryptoArgs: cfArgs)
|
||||
} else {
|
||||
try FileManager.default.moveItem(at: url, to: getAppFilePath(fileName))
|
||||
savedFile = CryptoFile.plain(fileName)
|
||||
}
|
||||
ChatModel.shared.filesToDelete.remove(url)
|
||||
return CryptoFile.plain(fileName)
|
||||
return savedFile
|
||||
} catch {
|
||||
logger.error("ImageUtils.moveTempFileFromURL error: \(error.localizedDescription)")
|
||||
return nil
|
||||
|
||||
@@ -252,12 +252,6 @@ func apiSetFilesFolder(filesFolder: String) throws {
|
||||
throw r
|
||||
}
|
||||
|
||||
func setXFTPConfig(_ cfg: XFTPFileConfig?) throws {
|
||||
let r = chatSendCmdSync(.apiSetXFTPConfig(config: cfg))
|
||||
if case .cmdOk = r { return }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiSetEncryptLocalFiles(_ enable: Bool) throws {
|
||||
let r = chatSendCmdSync(.apiSetEncryptLocalFiles(enable: enable))
|
||||
if case .cmdOk = r { return }
|
||||
@@ -365,6 +359,13 @@ func apiSendMessage(type: ChatType, id: Int64, file: CryptoFile?, quotedItemId:
|
||||
}
|
||||
}
|
||||
|
||||
func apiCreateChatItem(noteFolderId: Int64, file: CryptoFile?, msg: MsgContent) async -> ChatItem? {
|
||||
let r = await chatSendCmd(.apiCreateChatItem(noteFolderId: noteFolderId, file: file, msg: msg))
|
||||
if case let .newChatItem(_, aChatItem) = r { return aChatItem.chatItem }
|
||||
createChatItemErrorAlert(r)
|
||||
return nil
|
||||
}
|
||||
|
||||
private func sendMessageErrorAlert(_ r: ChatResponse) {
|
||||
logger.error("apiSendMessage error: \(String(describing: r))")
|
||||
AlertManager.shared.showAlertMsg(
|
||||
@@ -373,6 +374,14 @@ private func sendMessageErrorAlert(_ r: ChatResponse) {
|
||||
)
|
||||
}
|
||||
|
||||
private func createChatItemErrorAlert(_ r: ChatResponse) {
|
||||
logger.error("apiCreateChatItem error: \(String(describing: r))")
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title: "Error creating message",
|
||||
message: "Error: \(String(describing: r))"
|
||||
)
|
||||
}
|
||||
|
||||
func apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent, live: Bool = false) async throws -> ChatItem {
|
||||
let r = await chatSendCmd(.apiUpdateChatItem(type: type, id: id, itemId: itemId, msg: msg, live: live), bgDelay: msgDelay)
|
||||
if case let .chatItemUpdated(_, aChatItem) = r { return aChatItem.chatItem }
|
||||
@@ -581,15 +590,15 @@ func apiVerifyGroupMember(_ groupId: Int64, _ groupMemberId: Int64, connectionCo
|
||||
return nil
|
||||
}
|
||||
|
||||
func apiAddContact(incognito: Bool) async -> (String, PendingContactConnection)? {
|
||||
func apiAddContact(incognito: Bool) async -> ((String, PendingContactConnection)?, Alert?) {
|
||||
guard let userId = ChatModel.shared.currentUser?.userId else {
|
||||
logger.error("apiAddContact: no current user")
|
||||
return nil
|
||||
return (nil, nil)
|
||||
}
|
||||
let r = await chatSendCmd(.apiAddContact(userId: userId, incognito: incognito), bgTask: false)
|
||||
if case let .invitation(_, connReqInvitation, connection) = r { return (connReqInvitation, connection) }
|
||||
AlertManager.shared.showAlert(connectionErrorAlert(r))
|
||||
return nil
|
||||
if case let .invitation(_, connReqInvitation, connection) = r { return ((connReqInvitation, connection), nil) }
|
||||
let alert = connectionErrorAlert(r)
|
||||
return (nil, alert)
|
||||
}
|
||||
|
||||
func apiSetConnectionIncognito(connId: Int64, incognito: Bool) async throws -> PendingContactConnection? {
|
||||
@@ -691,6 +700,9 @@ func apiConnectContactViaAddress(incognito: Bool, contactId: Int64) async -> (Co
|
||||
}
|
||||
|
||||
func apiDeleteChat(type: ChatType, id: Int64, notify: Bool? = nil) async throws {
|
||||
let chatId = type.rawValue + id.description
|
||||
DispatchQueue.main.async { ChatModel.shared.deletedChats.insert(chatId) }
|
||||
defer { DispatchQueue.main.async { ChatModel.shared.deletedChats.remove(chatId) } }
|
||||
let r = await chatSendCmd(.apiDeleteChat(type: type, id: id, notify: notify), bgTask: false)
|
||||
if case .direct = type, case .contactDeleted = r { return }
|
||||
if case .contactConnection = type, case .contactConnectionDeleted = r { return }
|
||||
@@ -852,8 +864,8 @@ func apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool) async throws {
|
||||
try await sendCommandOkResp(.apiChatUnread(type: type, id: id, unreadChat: unreadChat))
|
||||
}
|
||||
|
||||
func receiveFile(user: any UserLike, fileId: Int64, encrypted: Bool, auto: Bool = false) async {
|
||||
if let chatItem = await apiReceiveFile(fileId: fileId, encrypted: encrypted, auto: auto) {
|
||||
func receiveFile(user: any UserLike, fileId: Int64, auto: Bool = false) async {
|
||||
if let chatItem = await apiReceiveFile(fileId: fileId, encrypted: privacyEncryptLocalFilesGroupDefault.get(), auto: auto) {
|
||||
await chatItemSimpleUpdate(user, chatItem)
|
||||
}
|
||||
}
|
||||
@@ -1123,6 +1135,12 @@ func apiMemberRole(_ groupId: Int64, _ memberId: Int64, _ memberRole: GroupMembe
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiBlockMemberForAll(_ groupId: Int64, _ memberId: Int64, _ blocked: Bool) async throws -> GroupMember {
|
||||
let r = await chatSendCmd(.apiBlockMemberForAll(groupId: groupId, memberId: memberId, blocked: blocked), bgTask: false)
|
||||
if case let .memberBlockedForAllUser(_, _, member, _) = r { return member }
|
||||
throw r
|
||||
}
|
||||
|
||||
func leaveGroup(_ groupId: Int64) async {
|
||||
do {
|
||||
let groupInfo = try await apiLeaveGroup(groupId)
|
||||
@@ -1148,7 +1166,7 @@ func filterMembersToAdd(_ ms: [GMember]) -> [Contact] {
|
||||
let memberContactIds = ms.compactMap{ m in m.wrapped.memberCurrent ? m.wrapped.memberContactId : nil }
|
||||
return ChatModel.shared.chats
|
||||
.compactMap{ $0.chatInfo.contact }
|
||||
.filter{ !memberContactIds.contains($0.apiId) }
|
||||
.filter{ c in c.ready && c.active && !memberContactIds.contains(c.apiId) }
|
||||
.sorted{ $0.displayName.lowercased() < $1.displayName.lowercased() }
|
||||
}
|
||||
|
||||
@@ -1212,9 +1230,11 @@ private func currentUserId(_ funcName: String) throws -> Int64 {
|
||||
throw RuntimeError("\(funcName): no current user")
|
||||
}
|
||||
|
||||
func initializeChat(start: Bool, dbKey: String? = nil, refreshInvitations: Bool = true, confirmMigrations: MigrationConfirmation? = nil) throws {
|
||||
func initializeChat(start: Bool, confirmStart: Bool = false, dbKey: String? = nil, refreshInvitations: Bool = true, confirmMigrations: MigrationConfirmation? = nil) throws {
|
||||
logger.debug("initializeChat")
|
||||
let m = ChatModel.shared
|
||||
m.ctrlInitInProgress = true
|
||||
defer { m.ctrlInitInProgress = false }
|
||||
(m.chatDbEncrypted, m.chatDbStatus) = chatMigrateInit(dbKey, confirmMigrations: confirmMigrations)
|
||||
if m.chatDbStatus != .ok { return }
|
||||
// If we migrated successfully means previous re-encryption process on database level finished successfully too
|
||||
@@ -1223,7 +1243,6 @@ func initializeChat(start: Bool, dbKey: String? = nil, refreshInvitations: Bool
|
||||
}
|
||||
try apiSetTempFolder(tempFolder: getTempFilesDirectory().path)
|
||||
try apiSetFilesFolder(filesFolder: getAppFilesDirectory().path)
|
||||
try setXFTPConfig(getXFTPCfg())
|
||||
try apiSetEncryptLocalFiles(privacyEncryptLocalFilesGroupDefault.get())
|
||||
m.chatInitialized = true
|
||||
m.currentUser = try apiGetActiveUser()
|
||||
@@ -1231,7 +1250,37 @@ func initializeChat(start: Bool, dbKey: String? = nil, refreshInvitations: Bool
|
||||
onboardingStageDefault.set(.step1_SimpleXInfo)
|
||||
privacyDeliveryReceiptsSet.set(true)
|
||||
m.onboardingStage = .step1_SimpleXInfo
|
||||
} else if start {
|
||||
} else if confirmStart {
|
||||
showStartChatAfterRestartAlert { start in
|
||||
do {
|
||||
if start { AppChatState.shared.set(.active) }
|
||||
try chatInitialized(start: start, refreshInvitations: refreshInvitations)
|
||||
} catch let error {
|
||||
logger.error("ChatInitialized error: \(error)")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try chatInitialized(start: start, refreshInvitations: refreshInvitations)
|
||||
}
|
||||
}
|
||||
|
||||
func showStartChatAfterRestartAlert(result: @escaping (_ start: Bool) -> Void) {
|
||||
AlertManager.shared.showAlert(Alert(
|
||||
title: Text("Start chat?"),
|
||||
message: Text("Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat."),
|
||||
primaryButton: .default(Text("Ok")) {
|
||||
result(true)
|
||||
},
|
||||
secondaryButton: .cancel {
|
||||
result(false)
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
private func chatInitialized(start: Bool, refreshInvitations: Bool) throws {
|
||||
let m = ChatModel.shared
|
||||
if m.currentUser == nil { return }
|
||||
if start {
|
||||
try startChat(refreshInvitations: refreshInvitations)
|
||||
} else {
|
||||
m.chatRunning = false
|
||||
@@ -1289,8 +1338,12 @@ private func changeActiveUser_(_ userId: Int64, viewPwd: String?) throws {
|
||||
try getUserChatData()
|
||||
}
|
||||
|
||||
func changeActiveUserAsync_(_ userId: Int64, viewPwd: String?) async throws {
|
||||
let currentUser = try await apiSetActiveUserAsync(userId, viewPwd: viewPwd)
|
||||
func changeActiveUserAsync_(_ userId: Int64?, viewPwd: String?) async throws {
|
||||
let currentUser = if let userId = userId {
|
||||
try await apiSetActiveUserAsync(userId, viewPwd: viewPwd)
|
||||
} else {
|
||||
try apiGetActiveUser()
|
||||
}
|
||||
let users = try await listUsersAsync()
|
||||
await MainActor.run {
|
||||
let m = ChatModel.shared
|
||||
@@ -1299,7 +1352,7 @@ func changeActiveUserAsync_(_ userId: Int64, viewPwd: String?) async throws {
|
||||
}
|
||||
try await getUserChatDataAsync()
|
||||
await MainActor.run {
|
||||
if var (_, invitation) = ChatModel.shared.callInvitations.first(where: { _, inv in inv.user.userId == userId }) {
|
||||
if let currentUser = currentUser, var (_, invitation) = ChatModel.shared.callInvitations.first(where: { _, inv in inv.user.userId == userId }) {
|
||||
invitation.user = currentUser
|
||||
activateCall(invitation)
|
||||
}
|
||||
@@ -1315,14 +1368,21 @@ func getUserChatData() throws {
|
||||
}
|
||||
|
||||
private func getUserChatDataAsync() async throws {
|
||||
let userAddress = try await apiGetUserAddressAsync()
|
||||
let chatItemTTL = try await getChatItemTTLAsync()
|
||||
let chats = try await apiGetChatsAsync()
|
||||
await MainActor.run {
|
||||
let m = ChatModel.shared
|
||||
m.userAddress = userAddress
|
||||
m.chatItemTTL = chatItemTTL
|
||||
m.chats = chats.map { Chat.init($0) }
|
||||
let m = ChatModel.shared
|
||||
if m.currentUser != nil {
|
||||
let userAddress = try await apiGetUserAddressAsync()
|
||||
let chatItemTTL = try await getChatItemTTLAsync()
|
||||
let chats = try await apiGetChatsAsync()
|
||||
await MainActor.run {
|
||||
m.userAddress = userAddress
|
||||
m.chatItemTTL = chatItemTTL
|
||||
m.chats = chats.map { Chat.init($0) }
|
||||
}
|
||||
} else {
|
||||
await MainActor.run {
|
||||
m.userAddress = nil
|
||||
m.chats = []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1481,7 +1541,7 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
}
|
||||
if let file = cItem.autoReceiveFile() {
|
||||
Task {
|
||||
await receiveFile(user: user, fileId: file.fileId, encrypted: cItem.encryptLocalFile, auto: true)
|
||||
await receiveFile(user: user, fileId: file.fileId, auto: true)
|
||||
}
|
||||
}
|
||||
if cItem.showNotification {
|
||||
@@ -1619,6 +1679,13 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
_ = m.upsertGroupMember(groupInfo, member)
|
||||
}
|
||||
}
|
||||
case let .memberBlockedForAll(user, groupInfo, byMember: _, member: member, blocked: _):
|
||||
if active(user) {
|
||||
await MainActor.run {
|
||||
m.updateGroup(groupInfo)
|
||||
_ = m.upsertGroupMember(groupInfo, member)
|
||||
}
|
||||
}
|
||||
case let .newMemberContactReceivedInv(user, contact, _, _):
|
||||
if active(user) {
|
||||
await MainActor.run {
|
||||
@@ -1787,7 +1854,9 @@ func chatItemSimpleUpdate(_ user: any UserLike, _ aChatItem: AChatItem) async {
|
||||
let cItem = aChatItem.chatItem
|
||||
if active(user) {
|
||||
if await MainActor.run(body: { m.upsertChatItem(cInfo, cItem) }) {
|
||||
NtfManager.shared.notifyMessageReceived(user, cInfo, cItem)
|
||||
if cItem.showNotification {
|
||||
NtfManager.shared.notifyMessageReceived(user, cInfo, cItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,26 +107,16 @@ func initChatAndMigrate(refreshInvitations: Bool = true) {
|
||||
let m = ChatModel.shared
|
||||
if (!m.chatInitialized) {
|
||||
m.v3DBMigration = v3DBMigrationDefault.get()
|
||||
if AppChatState.shared.value == .stopped {
|
||||
AlertManager.shared.showAlert(Alert(
|
||||
title: Text("Start chat?"),
|
||||
message: Text("Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat."),
|
||||
primaryButton: .default(Text("Ok")) {
|
||||
AppChatState.shared.set(.active)
|
||||
initialize(start: true)
|
||||
},
|
||||
secondaryButton: .cancel {
|
||||
initialize(start: false)
|
||||
}
|
||||
))
|
||||
if AppChatState.shared.value == .stopped && storeDBPassphraseGroupDefault.get() && kcDatabasePassword.get() != nil {
|
||||
initialize(start: true, confirmStart: true)
|
||||
} else {
|
||||
initialize(start: true)
|
||||
}
|
||||
}
|
||||
|
||||
func initialize(start: Bool) {
|
||||
func initialize(start: Bool, confirmStart: Bool = false) {
|
||||
do {
|
||||
try initializeChat(start: m.v3DBMigration.startChat && start, refreshInvitations: refreshInvitations)
|
||||
try initializeChat(start: m.v3DBMigration.startChat && start, confirmStart: m.v3DBMigration.startChat && confirmStart, refreshInvitations: refreshInvitations)
|
||||
} catch let error {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title: start ? "Error starting chat" : "Error opening chat",
|
||||
|
||||
@@ -44,8 +44,10 @@ struct SimpleXApp: App {
|
||||
chatModel.appOpenUrl = url
|
||||
}
|
||||
.onAppear() {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
|
||||
initChatAndMigrate()
|
||||
if kcAppPassword.get() == nil || kcSelfDestructPassword.get() == nil {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
|
||||
initChatAndMigrate()
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: scenePhase) { phase in
|
||||
|
||||
@@ -52,7 +52,7 @@ struct CIFileView: View {
|
||||
private var itemInteractive: Bool {
|
||||
if let file = file {
|
||||
switch (file.fileStatus) {
|
||||
case .sndStored: return false
|
||||
case .sndStored: return file.fileProtocol == .local
|
||||
case .sndTransfer: return false
|
||||
case .sndComplete: return false
|
||||
case .sndCancelled: return false
|
||||
@@ -85,8 +85,7 @@ struct CIFileView: View {
|
||||
Task {
|
||||
logger.debug("CIFileView fileAction - in .rcvInvitation, in Task")
|
||||
if let user = m.currentUser {
|
||||
let encrypted = privacyEncryptLocalFilesGroupDefault.get()
|
||||
await receiveFile(user: user, fileId: file.fileId, encrypted: encrypted)
|
||||
await receiveFile(user: user, fileId: file.fileId)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -108,12 +107,18 @@ struct CIFileView: View {
|
||||
title: "Waiting for file",
|
||||
message: "File will be received when your contact is online, please wait or check later!"
|
||||
)
|
||||
case .local: ()
|
||||
}
|
||||
case .rcvComplete:
|
||||
logger.debug("CIFileView fileAction - in .rcvComplete")
|
||||
if let fileSource = getLoadedFileSource(file) {
|
||||
saveCryptoFile(fileSource)
|
||||
}
|
||||
case .sndStored:
|
||||
logger.debug("CIFileView fileAction - in .sndStored")
|
||||
if file.fileProtocol == .local, let fileSource = getLoadedFileSource(file) {
|
||||
saveCryptoFile(fileSource)
|
||||
}
|
||||
default: break
|
||||
}
|
||||
}
|
||||
@@ -126,11 +131,13 @@ struct CIFileView: View {
|
||||
switch file.fileProtocol {
|
||||
case .xftp: progressView()
|
||||
case .smp: fileIcon("doc.fill")
|
||||
case .local: fileIcon("doc.fill")
|
||||
}
|
||||
case let .sndTransfer(sndProgress, sndTotal):
|
||||
switch file.fileProtocol {
|
||||
case .xftp: progressCircle(sndProgress, sndTotal)
|
||||
case .smp: progressView()
|
||||
case .local: EmptyView()
|
||||
}
|
||||
case .sndComplete: fileIcon("doc.fill", innerIcon: "checkmark", innerIconSize: 10)
|
||||
case .sndCancelled: fileIcon("doc.fill", innerIcon: "xmark", innerIconSize: 10)
|
||||
|
||||
@@ -38,7 +38,7 @@ struct CIImageView: View {
|
||||
case .rcvInvitation:
|
||||
Task {
|
||||
if let user = m.currentUser {
|
||||
await receiveFile(user: user, fileId: file.fileId, encrypted: chatItem.encryptLocalFile)
|
||||
await receiveFile(user: user, fileId: file.fileId)
|
||||
}
|
||||
}
|
||||
case .rcvAccepted:
|
||||
@@ -53,6 +53,7 @@ struct CIImageView: View {
|
||||
title: "Waiting for image",
|
||||
message: "Image will be received when your contact is online, please wait or check later!"
|
||||
)
|
||||
case .local: ()
|
||||
}
|
||||
case .rcvTransfer: () // ?
|
||||
case .rcvComplete: () // ?
|
||||
@@ -90,6 +91,7 @@ struct CIImageView: View {
|
||||
switch file.fileProtocol {
|
||||
case .xftp: progressView()
|
||||
case .smp: EmptyView()
|
||||
case .local: EmptyView()
|
||||
}
|
||||
case .sndTransfer: progressView()
|
||||
case .sndComplete: fileIcon("checkmark", 10, 13)
|
||||
|
||||
@@ -26,6 +26,8 @@ struct CIVideoView: View {
|
||||
@State private var player: AVPlayer?
|
||||
@State private var fullPlayer: AVPlayer?
|
||||
@State private var url: URL?
|
||||
@State private var urlDecrypted: URL?
|
||||
@State private var decryptionInProgress: Bool = false
|
||||
@State private var showFullScreenPlayer = false
|
||||
@State private var timeObserver: Any? = nil
|
||||
@State private var fullScreenTimeObserver: Any? = nil
|
||||
@@ -39,8 +41,12 @@ struct CIVideoView: View {
|
||||
self._videoWidth = videoWidth
|
||||
self.scrollProxy = scrollProxy
|
||||
if let url = getLoadedVideo(chatItem.file) {
|
||||
self._player = State(initialValue: VideoPlayerView.getOrCreatePlayer(url, false))
|
||||
self._fullPlayer = State(initialValue: AVPlayer(url: url))
|
||||
let decrypted = chatItem.file?.fileSource?.cryptoArgs == nil ? url : chatItem.file?.fileSource?.decryptedGet()
|
||||
self._urlDecrypted = State(initialValue: decrypted)
|
||||
if let decrypted = decrypted {
|
||||
self._player = State(initialValue: VideoPlayerView.getOrCreatePlayer(decrypted, false))
|
||||
self._fullPlayer = State(initialValue: AVPlayer(url: decrypted))
|
||||
}
|
||||
self._url = State(initialValue: url)
|
||||
}
|
||||
if let data = Data(base64Encoded: dropImagePrefix(image)),
|
||||
@@ -53,8 +59,10 @@ struct CIVideoView: View {
|
||||
let file = chatItem.file
|
||||
ZStack {
|
||||
ZStack(alignment: .topLeading) {
|
||||
if let file = file, let preview = preview, let player = player, let url = url {
|
||||
videoView(player, url, file, preview, duration)
|
||||
if let file = file, let preview = preview, let player = player, let decrypted = urlDecrypted {
|
||||
videoView(player, decrypted, file, preview, duration)
|
||||
} else if let file = file, let defaultPreview = preview, file.loaded && urlDecrypted == nil {
|
||||
videoViewEncrypted(file, defaultPreview, duration)
|
||||
} else if let data = Data(base64Encoded: dropImagePrefix(image)),
|
||||
let uiImage = UIImage(data: data) {
|
||||
imageView(uiImage)
|
||||
@@ -62,7 +70,7 @@ struct CIVideoView: View {
|
||||
if let file = file {
|
||||
switch file.fileStatus {
|
||||
case .rcvInvitation:
|
||||
receiveFileIfValidSize(file: file, encrypted: false, receiveFile: receiveFile)
|
||||
receiveFileIfValidSize(file: file, receiveFile: receiveFile)
|
||||
case .rcvAccepted:
|
||||
switch file.fileProtocol {
|
||||
case .xftp:
|
||||
@@ -75,6 +83,7 @@ struct CIVideoView: View {
|
||||
title: "Waiting for video",
|
||||
message: "Video will be received when your contact is online, please wait or check later!"
|
||||
)
|
||||
case .local: ()
|
||||
}
|
||||
case .rcvTransfer: () // ?
|
||||
case .rcvComplete: () // ?
|
||||
@@ -88,7 +97,7 @@ struct CIVideoView: View {
|
||||
}
|
||||
if let file = file, case .rcvInvitation = file.fileStatus {
|
||||
Button {
|
||||
receiveFileIfValidSize(file: file, encrypted: false, receiveFile: receiveFile)
|
||||
receiveFileIfValidSize(file: file, receiveFile: receiveFile)
|
||||
} label: {
|
||||
playPauseIcon("play.fill")
|
||||
}
|
||||
@@ -96,12 +105,46 @@ struct CIVideoView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func videoViewEncrypted(_ file: CIFile, _ defaultPreview: UIImage, _ duration: Int) -> some View {
|
||||
return ZStack(alignment: .topTrailing) {
|
||||
ZStack(alignment: .center) {
|
||||
let canBePlayed = !chatItem.chatDir.sent || file.fileStatus == CIFileStatus.sndComplete || (file.fileStatus == .sndStored && file.fileProtocol == .local)
|
||||
imageView(defaultPreview)
|
||||
.fullScreenCover(isPresented: $showFullScreenPlayer) {
|
||||
if let decrypted = urlDecrypted {
|
||||
fullScreenPlayer(decrypted)
|
||||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
decrypt(file: file) {
|
||||
showFullScreenPlayer = urlDecrypted != nil
|
||||
}
|
||||
}
|
||||
if !decryptionInProgress {
|
||||
Button {
|
||||
decrypt(file: file) {
|
||||
if let decrypted = urlDecrypted {
|
||||
videoPlaying = true
|
||||
player?.play()
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
playPauseIcon(canBePlayed ? "play.fill" : "play.slash")
|
||||
}
|
||||
.disabled(!canBePlayed)
|
||||
} else {
|
||||
videoDecryptionProgress()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func videoView(_ player: AVPlayer, _ url: URL, _ file: CIFile, _ preview: UIImage, _ duration: Int) -> some View {
|
||||
let w = preview.size.width <= preview.size.height ? maxWidth * 0.75 : maxWidth
|
||||
DispatchQueue.main.async { videoWidth = w }
|
||||
return ZStack(alignment: .topTrailing) {
|
||||
ZStack(alignment: .center) {
|
||||
let canBePlayed = !chatItem.chatDir.sent || file.fileStatus == CIFileStatus.sndComplete
|
||||
let canBePlayed = !chatItem.chatDir.sent || file.fileStatus == CIFileStatus.sndComplete || (file.fileStatus == .sndStored && file.fileProtocol == .local)
|
||||
VideoPlayerView(player: player, url: url, showControls: false)
|
||||
.frame(width: w, height: w * preview.size.height / preview.size.width)
|
||||
.onChange(of: m.stopPreviousRecPlay) { playingUrl in
|
||||
@@ -159,6 +202,16 @@ struct CIVideoView: View {
|
||||
.clipShape(Circle())
|
||||
}
|
||||
|
||||
private func videoDecryptionProgress(_ color: Color = .white) -> some View {
|
||||
ProgressView()
|
||||
.progressViewStyle(.circular)
|
||||
.frame(width: 12, height: 12)
|
||||
.tint(color)
|
||||
.frame(width: 40, height: 40)
|
||||
.background(Color.black.opacity(0.35))
|
||||
.clipShape(Circle())
|
||||
}
|
||||
|
||||
private func durationProgress() -> some View {
|
||||
HStack {
|
||||
Text("\(durationText(videoPlaying ? progress : duration))")
|
||||
@@ -202,11 +255,13 @@ struct CIVideoView: View {
|
||||
switch file.fileProtocol {
|
||||
case .xftp: progressView()
|
||||
case .smp: EmptyView()
|
||||
case .local: EmptyView()
|
||||
}
|
||||
case let .sndTransfer(sndProgress, sndTotal):
|
||||
switch file.fileProtocol {
|
||||
case .xftp: progressCircle(sndProgress, sndTotal)
|
||||
case .smp: progressView()
|
||||
case .local: EmptyView()
|
||||
}
|
||||
case .sndComplete: fileIcon("checkmark", 10, 13)
|
||||
case .sndCancelled: fileIcon("xmark", 10, 13)
|
||||
@@ -257,10 +312,10 @@ struct CIVideoView: View {
|
||||
}
|
||||
|
||||
// TODO encrypt: where file size is checked?
|
||||
private func receiveFileIfValidSize(file: CIFile, encrypted: Bool, receiveFile: @escaping (User, Int64, Bool, Bool) async -> Void) {
|
||||
private func receiveFileIfValidSize(file: CIFile, receiveFile: @escaping (User, Int64, Bool) async -> Void) {
|
||||
Task {
|
||||
if let user = m.currentUser {
|
||||
await receiveFile(user, file.fileId, encrypted, false)
|
||||
await receiveFile(user, file.fileId, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -323,6 +378,22 @@ struct CIVideoView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func decrypt(file: CIFile, completed: (() -> Void)? = nil) {
|
||||
if decryptionInProgress { return }
|
||||
decryptionInProgress = true
|
||||
Task {
|
||||
urlDecrypted = await file.fileSource?.decryptedGetOrCreate(&ChatModel.shared.filesToDelete)
|
||||
await MainActor.run {
|
||||
if let decrypted = urlDecrypted {
|
||||
player = VideoPlayerView.getOrCreatePlayer(decrypted, false)
|
||||
fullPlayer = AVPlayer(url: decrypted)
|
||||
}
|
||||
decryptionInProgress = true
|
||||
completed?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func addObserver(_ player: AVPlayer, _ url: URL) {
|
||||
timeObserver = player.addPeriodicTimeObserver(forInterval: CMTime(seconds: 0.01, preferredTimescale: CMTimeScale(NSEC_PER_SEC)), queue: .main) { time in
|
||||
if let item = player.currentItem {
|
||||
|
||||
@@ -221,7 +221,7 @@ struct VoiceMessagePlayer: View {
|
||||
Button {
|
||||
Task {
|
||||
if let user = chatModel.currentUser {
|
||||
await receiveFile(user: user, fileId: recordingFile.fileId, encrypted: privacyEncryptLocalFilesGroupDefault.get())
|
||||
await receiveFile(user: user, fileId: recordingFile.fileId)
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
let notesChatColorLight = Color(.sRGB, red: 0.27, green: 0.72, blue: 1, opacity: 0.21)
|
||||
let notesChatColorDark = Color(.sRGB, red: 0.27, green: 0.72, blue: 1, opacity: 0.19)
|
||||
let sentColorLight = Color(.sRGB, red: 0.27, green: 0.72, blue: 1, opacity: 0.12)
|
||||
let sentColorDark = Color(.sRGB, red: 0.27, green: 0.72, blue: 1, opacity: 0.17)
|
||||
private let sentQuoteColorLight = Color(.sRGB, red: 0.27, green: 0.72, blue: 1, opacity: 0.11)
|
||||
@@ -28,7 +30,9 @@ struct FramedItemView: View {
|
||||
@State var metaColor = Color.secondary
|
||||
@State var showFullScreenImage = false
|
||||
@Binding var allowMenu: Bool
|
||||
|
||||
@State private var showSecrets = false
|
||||
@State private var showQuoteSecrets = false
|
||||
|
||||
@Binding var audioPlayer: AudioPlayer?
|
||||
@Binding var playbackState: VoiceMessagePlaybackState
|
||||
@Binding var playbackTime: TimeInterval?
|
||||
@@ -42,7 +46,9 @@ struct FramedItemView: View {
|
||||
framedItemHeader(icon: "flag", caption: Text("moderated by \(byGroupMember.displayName)").italic())
|
||||
case .blocked:
|
||||
framedItemHeader(icon: "hand.raised", caption: Text("blocked").italic())
|
||||
default:
|
||||
case .blockedByAdmin:
|
||||
framedItemHeader(icon: "hand.raised", caption: Text("blocked by admin").italic())
|
||||
case .deleted:
|
||||
framedItemHeader(icon: "trash", caption: Text("marked deleted").italic())
|
||||
}
|
||||
} else if chatItem.meta.isLive {
|
||||
@@ -252,10 +258,12 @@ struct FramedItemView: View {
|
||||
}
|
||||
|
||||
private func ciQuotedMsgTextView(_ qi: CIQuote, lines: Int) -> some View {
|
||||
MsgContentView(chat: chat, text: qi.text, formattedText: qi.formattedText)
|
||||
.lineLimit(lines)
|
||||
.font(.subheadline)
|
||||
.padding(.bottom, 6)
|
||||
toggleSecrets(qi.formattedText, $showQuoteSecrets,
|
||||
MsgContentView(chat: chat, text: qi.text, formattedText: qi.formattedText, showSecrets: showQuoteSecrets)
|
||||
.lineLimit(lines)
|
||||
.font(.subheadline)
|
||||
.padding(.bottom, 6)
|
||||
)
|
||||
}
|
||||
|
||||
private func ciQuoteIconView(_ image: String) -> some View {
|
||||
@@ -278,13 +286,15 @@ struct FramedItemView: View {
|
||||
@ViewBuilder private func ciMsgContentView(_ ci: ChatItem) -> some View {
|
||||
let text = ci.meta.isLive ? ci.content.msgContent?.text ?? ci.text : ci.text
|
||||
let rtl = isRightToLeft(text)
|
||||
let v = MsgContentView(
|
||||
let ft = text == "" ? [] : ci.formattedText
|
||||
let v = toggleSecrets(ft, $showSecrets, MsgContentView(
|
||||
chat: chat,
|
||||
text: text,
|
||||
formattedText: text == "" ? [] : ci.formattedText,
|
||||
formattedText: ft,
|
||||
meta: ci.meta,
|
||||
rightToLeft: rtl
|
||||
)
|
||||
rightToLeft: rtl,
|
||||
showSecrets: showSecrets
|
||||
))
|
||||
.multilineTextAlignment(rtl ? .trailing : .leading)
|
||||
.padding(.vertical, 6)
|
||||
.padding(.horizontal, 12)
|
||||
@@ -298,7 +308,7 @@ struct FramedItemView: View {
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ViewBuilder private func ciFileView(_ ci: ChatItem, _ text: String) -> some View {
|
||||
CIFileView(file: chatItem.file, edited: chatItem.meta.itemEdited)
|
||||
.overlay(DetermineWidth())
|
||||
@@ -318,6 +328,14 @@ struct FramedItemView: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder func toggleSecrets<V: View>(_ ft: [FormattedText]?, _ showSecrets: Binding<Bool>, _ v: V) -> some View {
|
||||
if let ft = ft, ft.contains(where: { $0.isSecret }) {
|
||||
v.onTapGesture { showSecrets.wrappedValue.toggle() }
|
||||
} else {
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
func isRightToLeft(_ s: String) -> Bool {
|
||||
if let lang = CFStringTokenizerCopyBestStringLanguage(s as CFString, CFRange(location: 0, length: min(s.count, 80))) {
|
||||
return NSLocale.characterDirection(forLanguage: lang as String) == .rightToLeft
|
||||
|
||||
@@ -33,6 +33,7 @@ struct MarkedDeletedItemView: View {
|
||||
var i = m.getChatItemIndex(chatItem) {
|
||||
var moderated = 0
|
||||
var blocked = 0
|
||||
var blockedByAdmin = 0
|
||||
var deleted = 0
|
||||
var moderatedBy: Set<String> = []
|
||||
while i < m.reversedChatItems.count,
|
||||
@@ -44,16 +45,19 @@ struct MarkedDeletedItemView: View {
|
||||
moderated += 1
|
||||
moderatedBy.insert(byGroupMember.displayName)
|
||||
case .blocked: blocked += 1
|
||||
case .blockedByAdmin: blockedByAdmin += 1
|
||||
case .deleted: deleted += 1
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
let total = moderated + blocked + deleted
|
||||
let total = moderated + blocked + blockedByAdmin + deleted
|
||||
return total <= 1
|
||||
? markedDeletedText
|
||||
: total == moderated
|
||||
? "\(total) messages moderated by \(moderatedBy.joined(separator: ", "))"
|
||||
: total == blocked
|
||||
: total == blockedByAdmin
|
||||
? "\(total) messages blocked by admin"
|
||||
: total == blocked + blockedByAdmin
|
||||
? "\(total) messages blocked"
|
||||
: "\(total) messages marked deleted"
|
||||
} else {
|
||||
@@ -61,11 +65,14 @@ struct MarkedDeletedItemView: View {
|
||||
}
|
||||
}
|
||||
|
||||
// same texts are in markedDeletedText in ChatPreviewView, but it returns String;
|
||||
// can be refactored into a single function if functions calling these are changed to return same type
|
||||
var markedDeletedText: LocalizedStringKey {
|
||||
switch chatItem.meta.itemDeleted {
|
||||
case let .moderated(_, byGroupMember): "moderated by \(byGroupMember.displayName)"
|
||||
case .blocked: "blocked"
|
||||
default: "marked deleted"
|
||||
case .blockedByAdmin: "blocked by admin"
|
||||
case .deleted, nil: "marked deleted"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
private let uiLinkColor = UIColor(red: 0, green: 0.533, blue: 1, alpha: 1)
|
||||
let uiLinkColor = UIColor(red: 0, green: 0.533, blue: 1, alpha: 1)
|
||||
|
||||
private let noTyping = Text(" ")
|
||||
|
||||
@@ -31,6 +31,7 @@ struct MsgContentView: View {
|
||||
var sender: String? = nil
|
||||
var meta: CIMeta? = nil
|
||||
var rightToLeft = false
|
||||
var showSecrets: Bool
|
||||
@State private var typingIdx = 0
|
||||
@State private var timer: Timer?
|
||||
|
||||
@@ -62,7 +63,7 @@ struct MsgContentView: View {
|
||||
}
|
||||
|
||||
private func msgContentView() -> Text {
|
||||
var v = messageText(text, formattedText, sender)
|
||||
var v = messageText(text, formattedText, sender, showSecrets: showSecrets)
|
||||
if let mt = meta {
|
||||
if mt.isLive {
|
||||
v = v + typingIndicator(mt.recent)
|
||||
@@ -84,14 +85,14 @@ struct MsgContentView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func messageText(_ text: String, _ formattedText: [FormattedText]?, _ sender: String?, icon: String? = nil, preview: Bool = false) -> Text {
|
||||
func messageText(_ text: String, _ formattedText: [FormattedText]?, _ sender: String?, icon: String? = nil, preview: Bool = false, showSecrets: Bool) -> Text {
|
||||
let s = text
|
||||
var res: Text
|
||||
if let ft = formattedText, ft.count > 0 && ft.count <= 200 {
|
||||
res = formatText(ft[0], preview)
|
||||
res = formatText(ft[0], preview, showSecret: showSecrets)
|
||||
var i = 1
|
||||
while i < ft.count {
|
||||
res = res + formatText(ft[i], preview)
|
||||
res = res + formatText(ft[i], preview, showSecret: showSecrets)
|
||||
i = i + 1
|
||||
}
|
||||
} else {
|
||||
@@ -110,7 +111,7 @@ func messageText(_ text: String, _ formattedText: [FormattedText]?, _ sender: St
|
||||
}
|
||||
}
|
||||
|
||||
private func formatText(_ ft: FormattedText, _ preview: Bool) -> Text {
|
||||
private func formatText(_ ft: FormattedText, _ preview: Bool, showSecret: Bool) -> Text {
|
||||
let t = ft.text
|
||||
if let f = ft.format {
|
||||
switch (f) {
|
||||
@@ -118,7 +119,13 @@ private func formatText(_ ft: FormattedText, _ preview: Bool) -> Text {
|
||||
case .italic: return Text(t).italic()
|
||||
case .strikeThrough: return Text(t).strikethrough()
|
||||
case .snippet: return Text(t).font(.body.monospaced())
|
||||
case .secret: return Text(t).foregroundColor(.clear).underline(color: .primary)
|
||||
case .secret: return
|
||||
showSecret
|
||||
? Text(t)
|
||||
: Text(AttributedString(t, attributes: AttributeContainer([
|
||||
.foregroundColor: UIColor.clear as Any,
|
||||
.backgroundColor: UIColor.secondarySystemFill as Any
|
||||
])))
|
||||
case let .colored(color): return Text(t).foregroundColor(color.uiColor)
|
||||
case .uri: return linkText(t, t, preview, prefix: "")
|
||||
case let .simplexLink(linkType, simplexUri, smpHosts):
|
||||
@@ -144,7 +151,7 @@ private func linkText(_ s: String, _ link: String, _ preview: Bool, prefix: Stri
|
||||
]))).underline()
|
||||
}
|
||||
|
||||
private func simplexLinkText(_ linkType: SimplexLinkType, _ smpHosts: [String]) -> String {
|
||||
func simplexLinkText(_ linkType: SimplexLinkType, _ smpHosts: [String]) -> String {
|
||||
linkType.description + " " + "(via \(smpHosts.first ?? "?"))"
|
||||
}
|
||||
|
||||
@@ -156,7 +163,8 @@ struct MsgContentView_Previews: PreviewProvider {
|
||||
text: chatItem.text,
|
||||
formattedText: chatItem.formattedText,
|
||||
sender: chatItem.memberDisplayName,
|
||||
meta: chatItem.meta
|
||||
meta: chatItem.meta,
|
||||
showSecrets: false
|
||||
)
|
||||
.environmentObject(Chat.sampleData)
|
||||
}
|
||||
|
||||
@@ -53,7 +53,9 @@ struct ChatItemInfoView: View {
|
||||
}
|
||||
|
||||
private var title: String {
|
||||
ci.chatDir.sent
|
||||
ci.localNote
|
||||
? NSLocalizedString("Saved message", comment: "message info title")
|
||||
: ci.chatDir.sent
|
||||
? NSLocalizedString("Sent message", comment: "message info title")
|
||||
: NSLocalizedString("Received message", comment: "message info title")
|
||||
}
|
||||
@@ -110,7 +112,11 @@ struct ChatItemInfoView: View {
|
||||
.bold()
|
||||
.padding(.bottom)
|
||||
|
||||
infoRow("Sent at", localTimestamp(meta.itemTs))
|
||||
if ci.localNote {
|
||||
infoRow("Created at", localTimestamp(meta.itemTs))
|
||||
} else {
|
||||
infoRow("Sent at", localTimestamp(meta.itemTs))
|
||||
}
|
||||
if !ci.chatDir.sent {
|
||||
infoRow("Received at", localTimestamp(meta.createdAt))
|
||||
}
|
||||
@@ -168,7 +174,6 @@ struct ChatItemInfoView: View {
|
||||
@ViewBuilder private func itemVersionView(_ itemVersion: ChatItemVersion, _ maxWidth: CGFloat, current: Bool) -> some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
textBubble(itemVersion.msgContent.text, itemVersion.formattedText, nil)
|
||||
.allowsHitTesting(false)
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 6)
|
||||
.background(chatItemFrameColor(ci, colorScheme))
|
||||
@@ -198,7 +203,7 @@ struct ChatItemInfoView: View {
|
||||
|
||||
@ViewBuilder private func textBubble(_ text: String, _ formattedText: [FormattedText]?, _ sender: String? = nil) -> some View {
|
||||
if text != "" {
|
||||
messageText(text, formattedText, sender)
|
||||
TextBubble(text: text, formattedText: formattedText, sender: sender)
|
||||
} else {
|
||||
Text("no text")
|
||||
.italic()
|
||||
@@ -206,6 +211,17 @@ struct ChatItemInfoView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private struct TextBubble: View {
|
||||
var text: String
|
||||
var formattedText: [FormattedText]?
|
||||
var sender: String? = nil
|
||||
@State private var showSecrets = false
|
||||
|
||||
var body: some View {
|
||||
toggleSecrets(formattedText, $showSecrets, messageText(text, formattedText, sender, showSecrets: showSecrets))
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func quoteTab(_ qi: CIQuote) -> some View {
|
||||
GeometryReader { g in
|
||||
let maxWidth = (g.size.width - 32) * 0.84
|
||||
@@ -227,7 +243,6 @@ struct ChatItemInfoView: View {
|
||||
@ViewBuilder private func quotedMsgView(_ qi: CIQuote, _ maxWidth: CGFloat) -> some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
textBubble(qi.text, qi.formattedText, qi.getSender(nil))
|
||||
.allowsHitTesting(false)
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 6)
|
||||
.background(quotedMsgFrameColor(qi, colorScheme))
|
||||
@@ -341,7 +356,12 @@ struct ChatItemInfoView: View {
|
||||
private func itemInfoShareText() -> String {
|
||||
let meta = ci.meta
|
||||
var shareText: [String] = [String.localizedStringWithFormat(NSLocalizedString("# %@", comment: "copied message info title, # <title>"), title), ""]
|
||||
shareText += [String.localizedStringWithFormat(NSLocalizedString("Sent at: %@", comment: "copied message info"), localTimestamp(meta.itemTs))]
|
||||
shareText += [String.localizedStringWithFormat(
|
||||
ci.localNote
|
||||
? NSLocalizedString("Created at: %@", comment: "copied message info")
|
||||
: NSLocalizedString("Sent at: %@", comment: "copied message info"),
|
||||
localTimestamp(meta.itemTs))
|
||||
]
|
||||
if !ci.chatDir.sent {
|
||||
shareText += [String.localizedStringWithFormat(NSLocalizedString("Received at: %@", comment: "copied message info"), localTimestamp(meta.createdAt))]
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ struct ChatItemContentView<Content: View>: View {
|
||||
case let .rcvGroupFeatureRejected(feature): chatFeatureView(feature, .red)
|
||||
case .sndModerated: deletedItemView()
|
||||
case .rcvModerated: deletedItemView()
|
||||
case .rcvBlocked: deletedItemView()
|
||||
case let .invalidJSON(json): CIInvalidJSONView(json: json)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,18 +151,21 @@ struct ChatView: View {
|
||||
)
|
||||
)
|
||||
}
|
||||
} else if case .local = cInfo {
|
||||
ChatInfoToolbar(chat: chat)
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
switch cInfo {
|
||||
case let .direct(contact):
|
||||
HStack {
|
||||
if contact.allowsFeature(.calls) {
|
||||
let callsPrefEnabled = contact.mergedPreferences.calls.enabled.forUser
|
||||
if callsPrefEnabled {
|
||||
callButton(contact, .audio, imageName: "phone")
|
||||
.disabled(!contact.ready || !contact.active)
|
||||
}
|
||||
Menu {
|
||||
if contact.allowsFeature(.calls) {
|
||||
if callsPrefEnabled {
|
||||
Button {
|
||||
CallController.shared.startCall(contact, .video)
|
||||
} label: {
|
||||
@@ -205,6 +208,8 @@ struct ChatView: View {
|
||||
Image(systemName: "ellipsis")
|
||||
}
|
||||
}
|
||||
case .local:
|
||||
searchButton()
|
||||
default:
|
||||
EmptyView()
|
||||
}
|
||||
@@ -250,8 +255,8 @@ struct ChatView: View {
|
||||
}
|
||||
|
||||
private func searchToolbar() -> some View {
|
||||
HStack {
|
||||
HStack {
|
||||
HStack(spacing: 12) {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "magnifyingglass")
|
||||
TextField("Search", text: $searchText)
|
||||
.focused($searchFocussed)
|
||||
@@ -264,9 +269,9 @@ struct ChatView: View {
|
||||
Image(systemName: "xmark.circle.fill").opacity(searchText == "" ? 0 : 1)
|
||||
}
|
||||
}
|
||||
.padding(EdgeInsets(top: 8, leading: 6, bottom: 8, trailing: 6))
|
||||
.padding(EdgeInsets(top: 7, leading: 7, bottom: 7, trailing: 7))
|
||||
.foregroundColor(.secondary)
|
||||
.background(Color(.secondarySystemBackground))
|
||||
.background(Color(.tertiarySystemFill))
|
||||
.cornerRadius(10.0)
|
||||
|
||||
Button ("Cancel") {
|
||||
@@ -636,7 +641,7 @@ struct ChatView: View {
|
||||
Button("Delete for me", role: .destructive) {
|
||||
deleteMessage(.cidmInternal)
|
||||
}
|
||||
if let di = deletingItem, di.meta.editable {
|
||||
if let di = deletingItem, di.meta.editable && !di.localNote {
|
||||
Button(broadcastDeleteButtonText, role: .destructive) {
|
||||
deleteMessage(.cidmBroadcast)
|
||||
}
|
||||
@@ -720,7 +725,7 @@ struct ChatView: View {
|
||||
}
|
||||
menu.append(rm)
|
||||
}
|
||||
if ci.meta.itemDeleted == nil && !ci.isLiveDummy && !live {
|
||||
if ci.meta.itemDeleted == nil && !ci.isLiveDummy && !live && !ci.localNote {
|
||||
menu.append(replyUIAction(ci))
|
||||
}
|
||||
let fileSource = getLoadedFileSource(ci.file)
|
||||
@@ -744,13 +749,15 @@ struct ChatView: View {
|
||||
if ci.meta.editable && !mc.isVoice && !live {
|
||||
menu.append(editAction(ci))
|
||||
}
|
||||
menu.append(viewInfoUIAction(ci))
|
||||
if !ci.isLiveDummy {
|
||||
menu.append(viewInfoUIAction(ci))
|
||||
}
|
||||
if revealed {
|
||||
menu.append(hideUIAction())
|
||||
}
|
||||
if ci.meta.itemDeleted == nil,
|
||||
if ci.meta.itemDeleted == nil && !ci.localNote,
|
||||
let file = ci.file,
|
||||
let cancelAction = file.cancelAction {
|
||||
let cancelAction = file.cancelAction {
|
||||
menu.append(cancelFileUIAction(file.fileId, cancelAction))
|
||||
}
|
||||
if !live || !ci.meta.isLive {
|
||||
|
||||
@@ -295,7 +295,7 @@ struct ComposeView: View {
|
||||
sendMessage(ttl: ttl)
|
||||
resetLinkPreview()
|
||||
},
|
||||
sendLiveMessage: sendLiveMessage,
|
||||
sendLiveMessage: chat.chatInfo.chatType != .local ? sendLiveMessage : nil,
|
||||
updateLiveMessage: updateLiveMessage,
|
||||
cancelLiveMessage: {
|
||||
composeState.liveMessage = nil
|
||||
@@ -689,7 +689,7 @@ struct ComposeView: View {
|
||||
let file = voiceCryptoFile(recordingFileName)
|
||||
sent = await send(.voice(text: msgText, duration: duration), quoted: quoted, file: file, ttl: ttl)
|
||||
case let .filePreview(_, file):
|
||||
if let savedFile = saveFileFromURL(file, encrypted: privacyEncryptLocalFilesGroupDefault.get()) {
|
||||
if let savedFile = saveFileFromURL(file) {
|
||||
sent = await send(.file(msgText), quoted: quoted, file: savedFile, live: live, ttl: ttl)
|
||||
}
|
||||
}
|
||||
@@ -792,15 +792,17 @@ struct ComposeView: View {
|
||||
}
|
||||
|
||||
func send(_ mc: MsgContent, quoted: Int64?, file: CryptoFile? = nil, live: Bool = false, ttl: Int?) async -> ChatItem? {
|
||||
if let chatItem = await apiSendMessage(
|
||||
type: chat.chatInfo.chatType,
|
||||
id: chat.chatInfo.apiId,
|
||||
file: file,
|
||||
quotedItemId: quoted,
|
||||
msg: mc,
|
||||
live: live,
|
||||
ttl: ttl
|
||||
) {
|
||||
if let chatItem = chat.chatInfo.chatType == .local
|
||||
? await apiCreateChatItem(noteFolderId: chat.chatInfo.apiId, file: file, msg: mc)
|
||||
: await apiSendMessage(
|
||||
type: chat.chatInfo.chatType,
|
||||
id: chat.chatInfo.apiId,
|
||||
file: file,
|
||||
quotedItemId: quoted,
|
||||
msg: mc,
|
||||
live: live,
|
||||
ttl: ttl
|
||||
) {
|
||||
await MainActor.run {
|
||||
chatModel.removeLiveDummy(animated: false)
|
||||
chatModel.addChatItem(chat.chatInfo, chatItem)
|
||||
@@ -976,6 +978,9 @@ struct ComposeView: View {
|
||||
}
|
||||
|
||||
private func cancelLinkPreview() {
|
||||
if let pendingLink = pendingLinkUrl?.absoluteString {
|
||||
cancelledLinks.insert(pendingLink)
|
||||
}
|
||||
if let uri = composeState.linkPreview?.uri.absoluteString {
|
||||
cancelledLinks.insert(uri)
|
||||
}
|
||||
|
||||
@@ -51,7 +51,8 @@ struct ContextItemView: View {
|
||||
MsgContentView(
|
||||
chat: chat,
|
||||
text: contextItem.text,
|
||||
formattedText: contextItem.formattedText
|
||||
formattedText: contextItem.formattedText,
|
||||
showSecrets: false
|
||||
)
|
||||
.multilineTextAlignment(isRightToLeft(contextItem.text) ? .trailing : .leading)
|
||||
.lineLimit(lines)
|
||||
|
||||
@@ -116,7 +116,6 @@ struct ContactPreferencesView: View {
|
||||
|
||||
private func featureFooter(_ feature: ChatFeature, _ enabled: FeatureEnabled) -> some View {
|
||||
Text(feature.enabledDescription(enabled))
|
||||
.frame(height: 36, alignment: .topLeading)
|
||||
}
|
||||
|
||||
private func savePreferences() {
|
||||
|
||||
@@ -36,6 +36,8 @@ struct GroupChatInfoView: View {
|
||||
case largeGroupReceiptsDisabled
|
||||
case blockMemberAlert(mem: GroupMember)
|
||||
case unblockMemberAlert(mem: GroupMember)
|
||||
case blockForAllAlert(mem: GroupMember)
|
||||
case unblockForAllAlert(mem: GroupMember)
|
||||
case removeMemberAlert(mem: GroupMember)
|
||||
case error(title: LocalizedStringKey, error: LocalizedStringKey)
|
||||
|
||||
@@ -48,6 +50,8 @@ struct GroupChatInfoView: View {
|
||||
case .largeGroupReceiptsDisabled: return "largeGroupReceiptsDisabled"
|
||||
case let .blockMemberAlert(mem): return "blockMemberAlert \(mem.groupMemberId)"
|
||||
case let .unblockMemberAlert(mem): return "unblockMemberAlert \(mem.groupMemberId)"
|
||||
case let .blockForAllAlert(mem): return "blockForAllAlert \(mem.groupMemberId)"
|
||||
case let .unblockForAllAlert(mem): return "unblockForAllAlert \(mem.groupMemberId)"
|
||||
case let .removeMemberAlert(mem): return "removeMemberAlert \(mem.groupMemberId)"
|
||||
case let .error(title, _): return "error \(title)"
|
||||
}
|
||||
@@ -143,6 +147,8 @@ struct GroupChatInfoView: View {
|
||||
case .largeGroupReceiptsDisabled: return largeGroupReceiptsDisabledAlert()
|
||||
case let .blockMemberAlert(mem): return blockMemberAlert(groupInfo, mem)
|
||||
case let .unblockMemberAlert(mem): return unblockMemberAlert(groupInfo, mem)
|
||||
case let .blockForAllAlert(mem): return blockForAllAlert(groupInfo, mem)
|
||||
case let .unblockForAllAlert(mem): return unblockForAllAlert(groupInfo, mem)
|
||||
case let .removeMemberAlert(mem): return removeMemberAlert(mem)
|
||||
case let .error(title, error): return Alert(title: Text(title), message: Text(error))
|
||||
}
|
||||
@@ -226,13 +232,10 @@ struct GroupChatInfoView: View {
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
Spacer()
|
||||
let role = member.memberRole
|
||||
if role == .owner || role == .admin {
|
||||
Text(member.memberRole.text)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
memberInfo(member)
|
||||
}
|
||||
|
||||
// revert from this:
|
||||
if user {
|
||||
v
|
||||
} else if member.canBeRemoved(groupInfo: groupInfo) {
|
||||
@@ -240,6 +243,43 @@ struct GroupChatInfoView: View {
|
||||
} else {
|
||||
blockSwipe(member, v)
|
||||
}
|
||||
// revert to this: vvv
|
||||
// if user {
|
||||
// v
|
||||
// } else if groupInfo.membership.memberRole >= .admin {
|
||||
// // TODO if there are more actions, refactor with lists of swipeActions
|
||||
// let canBlockForAll = member.canBlockForAll(groupInfo: groupInfo)
|
||||
// let canRemove = member.canBeRemoved(groupInfo: groupInfo)
|
||||
// if canBlockForAll && canRemove {
|
||||
// removeSwipe(member, blockForAllSwipe(member, v))
|
||||
// } else if canBlockForAll {
|
||||
// blockForAllSwipe(member, v)
|
||||
// } else if canRemove {
|
||||
// removeSwipe(member, v)
|
||||
// } else {
|
||||
// v
|
||||
// }
|
||||
// } else {
|
||||
// if !member.blockedByAdmin {
|
||||
// blockSwipe(member, v)
|
||||
// } else {
|
||||
// v
|
||||
// }
|
||||
// }
|
||||
// ^^^
|
||||
}
|
||||
|
||||
@ViewBuilder private func memberInfo(_ member: GroupMember) -> some View {
|
||||
if member.blocked {
|
||||
Text("blocked")
|
||||
.foregroundColor(.secondary)
|
||||
} else {
|
||||
let role = member.memberRole
|
||||
if [.owner, .admin, .observer].contains(role) {
|
||||
Text(member.memberRole.text)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func blockSwipe<V: View>(_ member: GroupMember, _ v: V) -> some View {
|
||||
@@ -260,6 +300,24 @@ struct GroupChatInfoView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func blockForAllSwipe<V: View>(_ member: GroupMember, _ v: V) -> some View {
|
||||
v.swipeActions(edge: .leading) {
|
||||
if member.blockedByAdmin {
|
||||
Button {
|
||||
alert = .unblockForAllAlert(mem: member)
|
||||
} label: {
|
||||
Label("Unblock for all", systemImage: "hand.raised.slash").foregroundColor(.accentColor)
|
||||
}
|
||||
} else {
|
||||
Button {
|
||||
alert = .blockForAllAlert(mem: member)
|
||||
} label: {
|
||||
Label("Block for all", systemImage: "hand.raised").foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func removeSwipe<V: View>(_ member: GroupMember, _ v: V) -> some View {
|
||||
v.swipeActions(edge: .trailing) {
|
||||
Button(role: .destructive) {
|
||||
@@ -312,7 +370,11 @@ struct GroupChatInfoView: View {
|
||||
|
||||
private func addOrEditWelcomeMessage() -> some View {
|
||||
NavigationLink {
|
||||
GroupWelcomeView(groupId: groupInfo.groupId, groupInfo: $groupInfo)
|
||||
GroupWelcomeView(
|
||||
groupInfo: $groupInfo,
|
||||
groupProfile: groupInfo.groupProfile,
|
||||
welcomeText: groupInfo.groupProfile.description ?? ""
|
||||
)
|
||||
.navigationTitle("Welcome message")
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
} label: {
|
||||
|
||||
@@ -18,6 +18,7 @@ struct GroupLinkView: View {
|
||||
var linkCreatedCb: (() -> Void)? = nil
|
||||
@State private var creatingLink = false
|
||||
@State private var alert: GroupLinkAlert?
|
||||
@State private var shouldCreate = true
|
||||
|
||||
private enum GroupLinkAlert: Identifiable {
|
||||
case deleteLink
|
||||
@@ -70,6 +71,7 @@ struct GroupLinkView: View {
|
||||
}
|
||||
.frame(height: 36)
|
||||
SimpleXLinkQRCode(uri: groupLink)
|
||||
.id("simplex-qrcode-view-for-\(groupLink)")
|
||||
Button {
|
||||
showShareSheet(items: [simplexChatLink(groupLink)])
|
||||
} label: {
|
||||
@@ -125,9 +127,10 @@ struct GroupLinkView: View {
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if groupLink == nil && !creatingLink {
|
||||
if groupLink == nil && !creatingLink && shouldCreate {
|
||||
createGroupLink()
|
||||
}
|
||||
shouldCreate = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ struct GroupMemberInfoView: View {
|
||||
enum GroupMemberInfoViewAlert: Identifiable {
|
||||
case blockMemberAlert(mem: GroupMember)
|
||||
case unblockMemberAlert(mem: GroupMember)
|
||||
case blockForAllAlert(mem: GroupMember)
|
||||
case unblockForAllAlert(mem: GroupMember)
|
||||
case removeMemberAlert(mem: GroupMember)
|
||||
case changeMemberRoleAlert(mem: GroupMember, role: GroupMemberRole)
|
||||
case switchAddressAlert
|
||||
@@ -39,6 +41,8 @@ struct GroupMemberInfoView: View {
|
||||
switch self {
|
||||
case let .blockMemberAlert(mem): return "blockMemberAlert \(mem.groupMemberId)"
|
||||
case let .unblockMemberAlert(mem): return "unblockMemberAlert \(mem.groupMemberId)"
|
||||
case let .blockForAllAlert(mem): return "blockForAllAlert \(mem.groupMemberId)"
|
||||
case let .unblockForAllAlert(mem): return "unblockForAllAlert \(mem.groupMemberId)"
|
||||
case let .removeMemberAlert(mem): return "removeMemberAlert \(mem.groupMemberId)"
|
||||
case let .changeMemberRoleAlert(mem, role): return "changeMemberRoleAlert \(mem.groupMemberId) \(role.rawValue)"
|
||||
case .switchAddressAlert: return "switchAddressAlert"
|
||||
@@ -164,6 +168,7 @@ struct GroupMemberInfoView: View {
|
||||
}
|
||||
}
|
||||
|
||||
// revert from this:
|
||||
Section {
|
||||
if member.memberSettings.showMessages {
|
||||
blockMemberButton(member)
|
||||
@@ -171,9 +176,16 @@ struct GroupMemberInfoView: View {
|
||||
unblockMemberButton(member)
|
||||
}
|
||||
if member.canBeRemoved(groupInfo: groupInfo) {
|
||||
removeMemberButton(member)
|
||||
removeMemberButton(member)
|
||||
}
|
||||
}
|
||||
// revert to this: vvv
|
||||
// if groupInfo.membership.memberRole >= .admin {
|
||||
// adminDestructiveSection(member)
|
||||
// } else {
|
||||
// nonAdminBlockSection(member)
|
||||
// }
|
||||
// ^^^
|
||||
|
||||
if developerTools {
|
||||
Section("For console") {
|
||||
@@ -216,6 +228,8 @@ struct GroupMemberInfoView: View {
|
||||
switch(alertItem) {
|
||||
case let .blockMemberAlert(mem): return blockMemberAlert(groupInfo, mem)
|
||||
case let .unblockMemberAlert(mem): return unblockMemberAlert(groupInfo, mem)
|
||||
case let .blockForAllAlert(mem): return blockForAllAlert(groupInfo, mem)
|
||||
case let .unblockForAllAlert(mem): return unblockForAllAlert(groupInfo, mem)
|
||||
case let .removeMemberAlert(mem): return removeMemberAlert(mem)
|
||||
case let .changeMemberRoleAlert(mem, _): return changeMemberRoleAlert(mem)
|
||||
case .switchAddressAlert: return switchAddressAlert(switchMemberAddress)
|
||||
@@ -385,6 +399,55 @@ struct GroupMemberInfoView: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func adminDestructiveSection(_ mem: GroupMember) -> some View {
|
||||
let canBlockForAll = mem.canBlockForAll(groupInfo: groupInfo)
|
||||
let canRemove = mem.canBeRemoved(groupInfo: groupInfo)
|
||||
if canBlockForAll || canRemove {
|
||||
Section {
|
||||
if canBlockForAll {
|
||||
if mem.blockedByAdmin {
|
||||
unblockForAllButton(mem)
|
||||
} else {
|
||||
blockForAllButton(mem)
|
||||
}
|
||||
}
|
||||
if canRemove {
|
||||
removeMemberButton(mem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func nonAdminBlockSection(_ mem: GroupMember) -> some View {
|
||||
Section {
|
||||
if mem.blockedByAdmin {
|
||||
Label("Blocked by admin", systemImage: "hand.raised")
|
||||
.foregroundColor(.secondary)
|
||||
} else if mem.memberSettings.showMessages {
|
||||
blockMemberButton(mem)
|
||||
} else {
|
||||
unblockMemberButton(mem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func blockForAllButton(_ mem: GroupMember) -> some View {
|
||||
Button(role: .destructive) {
|
||||
alert = .blockForAllAlert(mem: mem)
|
||||
} label: {
|
||||
Label("Block for all", systemImage: "hand.raised")
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
|
||||
private func unblockForAllButton(_ mem: GroupMember) -> some View {
|
||||
Button {
|
||||
alert = .unblockForAllAlert(mem: mem)
|
||||
} label: {
|
||||
Label("Unblock for all", systemImage: "hand.raised.slash")
|
||||
}
|
||||
}
|
||||
|
||||
private func blockMemberButton(_ mem: GroupMember) -> some View {
|
||||
Button(role: .destructive) {
|
||||
alert = .blockMemberAlert(mem: mem)
|
||||
@@ -560,6 +623,41 @@ func updateMemberSettings(_ gInfo: GroupInfo, _ member: GroupMember, _ memberSet
|
||||
}
|
||||
}
|
||||
|
||||
func blockForAllAlert(_ gInfo: GroupInfo, _ mem: GroupMember) -> Alert {
|
||||
Alert(
|
||||
title: Text("Block member for all?"),
|
||||
message: Text("All new messages from \(mem.chatViewName) will be hidden!"),
|
||||
primaryButton: .destructive(Text("Block for all")) {
|
||||
blockMemberForAll(gInfo, mem, true)
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
|
||||
func unblockForAllAlert(_ gInfo: GroupInfo, _ mem: GroupMember) -> Alert {
|
||||
Alert(
|
||||
title: Text("Unblock member for all?"),
|
||||
message: Text("Messages from \(mem.chatViewName) will be shown!"),
|
||||
primaryButton: .default(Text("Unblock for all")) {
|
||||
blockMemberForAll(gInfo, mem, false)
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
|
||||
func blockMemberForAll(_ gInfo: GroupInfo, _ member: GroupMember, _ blocked: Bool) {
|
||||
Task {
|
||||
do {
|
||||
let updatedMember = try await apiBlockMemberForAll(gInfo.groupId, member.groupMemberId, blocked)
|
||||
await MainActor.run {
|
||||
_ = ChatModel.shared.upsertGroupMember(gInfo, updatedMember)
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("apiBlockMemberForAll error: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct GroupMemberInfoView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
GroupMemberInfoView(
|
||||
|
||||
@@ -28,6 +28,7 @@ struct GroupPreferencesView: View {
|
||||
featureSection(.reactions, $preferences.reactions.enable)
|
||||
featureSection(.voice, $preferences.voice.enable)
|
||||
featureSection(.files, $preferences.files.enable)
|
||||
featureSection(.history, $preferences.history.enable)
|
||||
|
||||
if groupInfo.canEdit {
|
||||
Section {
|
||||
@@ -96,7 +97,6 @@ struct GroupPreferencesView: View {
|
||||
}
|
||||
} footer: {
|
||||
Text(feature.enableDescription(enableFeature.wrappedValue, groupInfo.canEdit))
|
||||
.frame(height: 36, alignment: .topLeading)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,29 +11,32 @@ import SimpleXChat
|
||||
|
||||
struct GroupWelcomeView: View {
|
||||
@Environment(\.dismiss) var dismiss: DismissAction
|
||||
@EnvironmentObject private var m: ChatModel
|
||||
var groupId: Int64
|
||||
@Binding var groupInfo: GroupInfo
|
||||
@State private var welcomeText: String = ""
|
||||
@State var groupProfile: GroupProfile
|
||||
@State var welcomeText: String
|
||||
@State private var editMode = true
|
||||
@FocusState private var keyboardVisible: Bool
|
||||
@State private var showSaveDialog = false
|
||||
|
||||
let maxByteCount = 1200
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
if groupInfo.canEdit {
|
||||
editorView()
|
||||
.modifier(BackButton {
|
||||
if welcomeText == groupInfo.groupProfile.description || (welcomeText == "" && groupInfo.groupProfile.description == nil) {
|
||||
if welcomeTextUnchanged() {
|
||||
dismiss()
|
||||
} else {
|
||||
showSaveDialog = true
|
||||
}
|
||||
})
|
||||
.confirmationDialog("Save welcome message?", isPresented: $showSaveDialog) {
|
||||
Button("Save and update group profile") {
|
||||
save()
|
||||
dismiss()
|
||||
.confirmationDialog(
|
||||
welcomeTextFitsLimit() ? "Save welcome message?" : "Welcome message is too long",
|
||||
isPresented: $showSaveDialog
|
||||
) {
|
||||
if welcomeTextFitsLimit() {
|
||||
Button("Save and update group profile") { save() }
|
||||
}
|
||||
Button("Exit without saving") { dismiss() }
|
||||
}
|
||||
@@ -47,15 +50,15 @@ struct GroupWelcomeView: View {
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
welcomeText = groupInfo.groupProfile.description ?? ""
|
||||
keyboardVisible = true
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
keyboardVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func textPreview() -> some View {
|
||||
messageText(welcomeText, parseSimpleXMarkdown(welcomeText), nil)
|
||||
.allowsHitTesting(false)
|
||||
.frame(minHeight: 140, alignment: .topLeading)
|
||||
messageText(welcomeText, parseSimpleXMarkdown(welcomeText), nil, showSecrets: false)
|
||||
.frame(minHeight: 130, alignment: .topLeading)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
|
||||
@@ -75,7 +78,7 @@ struct GroupWelcomeView: View {
|
||||
}
|
||||
.padding(.horizontal, -5)
|
||||
.padding(.top, -8)
|
||||
.frame(height: 140, alignment: .topLeading)
|
||||
.frame(height: 130, alignment: .topLeading)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
} else {
|
||||
@@ -94,6 +97,9 @@ struct GroupWelcomeView: View {
|
||||
}
|
||||
.disabled(welcomeText.isEmpty)
|
||||
copyButton()
|
||||
} footer: {
|
||||
Text(!welcomeTextFitsLimit() ? "Message too large" : "")
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
|
||||
Section {
|
||||
@@ -114,7 +120,15 @@ struct GroupWelcomeView: View {
|
||||
Button("Save and update group profile") {
|
||||
save()
|
||||
}
|
||||
.disabled(welcomeText == groupInfo.groupProfile.description || (welcomeText == "" && groupInfo.groupProfile.description == nil))
|
||||
.disabled(welcomeTextUnchanged() || !welcomeTextFitsLimit())
|
||||
}
|
||||
|
||||
private func welcomeTextUnchanged() -> Bool {
|
||||
welcomeText == groupInfo.groupProfile.description || (welcomeText == "" && groupInfo.groupProfile.description == nil)
|
||||
}
|
||||
|
||||
private func welcomeTextFitsLimit() -> Bool {
|
||||
chatJsonLength(welcomeText) <= maxByteCount
|
||||
}
|
||||
|
||||
private func save() {
|
||||
@@ -124,11 +138,13 @@ struct GroupWelcomeView: View {
|
||||
if welcome?.count == 0 {
|
||||
welcome = nil
|
||||
}
|
||||
var groupProfileUpdated = groupInfo.groupProfile
|
||||
groupProfileUpdated.description = welcome
|
||||
groupInfo = try await apiUpdateGroup(groupId, groupProfileUpdated)
|
||||
m.updateGroup(groupInfo)
|
||||
welcomeText = welcome ?? ""
|
||||
groupProfile.description = welcome
|
||||
let gInfo = try await apiUpdateGroup(groupInfo.groupId, groupProfile)
|
||||
await MainActor.run {
|
||||
groupInfo = gInfo
|
||||
ChatModel.shared.updateGroup(gInfo)
|
||||
dismiss()
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("apiUpdateGroup error: \(responseError(error))")
|
||||
}
|
||||
@@ -138,6 +154,6 @@ struct GroupWelcomeView: View {
|
||||
|
||||
struct GroupWelcomeView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
GroupWelcomeView(groupId: 1, groupInfo: Binding.constant(GroupInfo.sampleData))
|
||||
GroupProfileView(groupInfo: Binding.constant(GroupInfo.sampleData), groupProfile: GroupProfile.sampleData)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import SwiftUI
|
||||
struct ChatHelp: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@Binding var showSettings: Bool
|
||||
@State private var showAddChat = false
|
||||
@State private var newChatMenuOption: NewChatMenuOption? = nil
|
||||
|
||||
var body: some View {
|
||||
ScrollView { chatHelp() }
|
||||
@@ -39,13 +39,12 @@ struct ChatHelp: View {
|
||||
|
||||
HStack(spacing: 8) {
|
||||
Text("Tap button ")
|
||||
NewChatButton(showAddChat: $showAddChat)
|
||||
NewChatMenuButton(newChatMenuOption: $newChatMenuOption)
|
||||
Text("above, then choose:")
|
||||
}
|
||||
|
||||
Text("**Create link / QR code** for your contact to use.")
|
||||
Text("**Paste received link** or open it in the browser and tap **Open in mobile app**.")
|
||||
Text("**Scan QR code**: to connect to your contact in person or via video call.")
|
||||
Text("**Add contact**: to create a new invitation link, or connect via a link you received.")
|
||||
Text("**Create group**: to create a new group.")
|
||||
}
|
||||
.padding(.top, 24)
|
||||
|
||||
|
||||
@@ -44,6 +44,8 @@ struct ChatListNavLink: View {
|
||||
contactNavLink(contact)
|
||||
case let .group(groupInfo):
|
||||
groupNavLink(groupInfo)
|
||||
case let .local(noteFolder):
|
||||
noteFolderNavLink(noteFolder)
|
||||
case let .contactRequest(cReq):
|
||||
contactRequestNavLink(cReq)
|
||||
case let .contactConnection(cConn):
|
||||
@@ -195,6 +197,24 @@ struct ChatListNavLink: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func noteFolderNavLink(_ noteFolder: NoteFolder) -> some View {
|
||||
NavLinkPlain(
|
||||
tag: chat.chatInfo.id,
|
||||
selection: $chatModel.chatId,
|
||||
label: { ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) },
|
||||
disabled: !noteFolder.ready
|
||||
)
|
||||
.frame(height: rowHeights[dynamicTypeSize])
|
||||
.swipeActions(edge: .leading, allowsFullSwipe: true) {
|
||||
markReadButton()
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
if !chat.chatItems.isEmpty {
|
||||
clearNoteFolderButton()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func joinGroupButton() -> some View {
|
||||
Button {
|
||||
inProgress = true
|
||||
@@ -253,6 +273,15 @@ struct ChatListNavLink: View {
|
||||
.tint(Color.orange)
|
||||
}
|
||||
|
||||
private func clearNoteFolderButton() -> some View {
|
||||
Button {
|
||||
AlertManager.shared.showAlert(clearNoteFolderAlert())
|
||||
} label: {
|
||||
Label("Clear", systemImage: "gobackward")
|
||||
}
|
||||
.tint(Color.orange)
|
||||
}
|
||||
|
||||
private func leaveGroupChatButton(_ groupInfo: GroupInfo) -> some View {
|
||||
Button {
|
||||
AlertManager.shared.showAlert(leaveGroupAlert(groupInfo))
|
||||
@@ -357,6 +386,17 @@ struct ChatListNavLink: View {
|
||||
)
|
||||
}
|
||||
|
||||
private func clearNoteFolderAlert() -> Alert {
|
||||
Alert(
|
||||
title: Text("Clear private notes?"),
|
||||
message: Text("All messages will be deleted - this cannot be undone!"),
|
||||
primaryButton: .destructive(Text("Clear")) {
|
||||
Task { await clearChat(chat) }
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
|
||||
private func leaveGroupAlert(_ groupInfo: GroupInfo) -> Alert {
|
||||
Alert(
|
||||
title: Text("Leave group?"),
|
||||
|
||||
@@ -12,8 +12,12 @@ import SimpleXChat
|
||||
struct ChatListView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@Binding var showSettings: Bool
|
||||
@State private var searchMode = false
|
||||
@FocusState private var searchFocussed
|
||||
@State private var searchText = ""
|
||||
@State private var showAddChat = false
|
||||
@State private var searchShowingSimplexLink = false
|
||||
@State private var searchChatFilteredBySimplexLink: String? = nil
|
||||
@State private var newChatMenuOption: NewChatMenuOption? = nil
|
||||
@State private var userPickerVisible = false
|
||||
@State private var showConnectDesktop = false
|
||||
@AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false
|
||||
@@ -62,11 +66,7 @@ struct ChatListView: View {
|
||||
|
||||
private var chatListView: some View {
|
||||
VStack {
|
||||
if chatModel.chats.count > 0 {
|
||||
chatList.searchable(text: $searchText)
|
||||
} else {
|
||||
chatList
|
||||
}
|
||||
chatList
|
||||
}
|
||||
.onDisappear() { withAnimation { userPickerVisible = false } }
|
||||
.refreshable {
|
||||
@@ -85,9 +85,9 @@ struct ChatListView: View {
|
||||
secondaryButton: .cancel()
|
||||
))
|
||||
}
|
||||
.offset(x: -8)
|
||||
.listStyle(.plain)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarHidden(searchMode)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
let user = chatModel.currentUser ?? User.sampleData
|
||||
@@ -124,7 +124,7 @@ struct ChatListView: View {
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
switch chatModel.chatRunning {
|
||||
case .some(true): NewChatButton(showAddChat: $showAddChat)
|
||||
case .some(true): NewChatMenuButton(newChatMenuOption: $newChatMenuOption)
|
||||
case .some(false): chatStoppedIcon()
|
||||
case .none: EmptyView()
|
||||
}
|
||||
@@ -144,11 +144,25 @@ struct ChatListView: View {
|
||||
@ViewBuilder private var chatList: some View {
|
||||
let cs = filteredChats()
|
||||
ZStack {
|
||||
List {
|
||||
ForEach(cs, id: \.viewId) { chat in
|
||||
ChatListNavLink(chat: chat)
|
||||
.padding(.trailing, -16)
|
||||
.disabled(chatModel.chatRunning != true)
|
||||
VStack {
|
||||
List {
|
||||
if !chatModel.chats.isEmpty {
|
||||
ChatListSearchBar(
|
||||
searchMode: $searchMode,
|
||||
searchFocussed: $searchFocussed,
|
||||
searchText: $searchText,
|
||||
searchShowingSimplexLink: $searchShowingSimplexLink,
|
||||
searchChatFilteredBySimplexLink: $searchChatFilteredBySimplexLink
|
||||
)
|
||||
.listRowSeparator(.hidden)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
ForEach(cs, id: \.viewId) { chat in
|
||||
ChatListNavLink(chat: chat)
|
||||
.padding(.trailing, -16)
|
||||
.disabled(chatModel.chatRunning != true || chatModel.deletedChats.contains(chat.chatInfo.id))
|
||||
}
|
||||
.offset(x: -8)
|
||||
}
|
||||
}
|
||||
.onChange(of: chatModel.chatId) { _ in
|
||||
@@ -182,7 +196,7 @@ struct ChatListView: View {
|
||||
.padding(.trailing, 12)
|
||||
|
||||
connectButton("Tap to start a new chat") {
|
||||
showAddChat = true
|
||||
newChatMenuOption = .newContact
|
||||
}
|
||||
|
||||
Spacer()
|
||||
@@ -214,22 +228,27 @@ struct ChatListView: View {
|
||||
}
|
||||
|
||||
private func filteredChats() -> [Chat] {
|
||||
let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase
|
||||
return s == "" && !showUnreadAndFavorites
|
||||
if let linkChatId = searchChatFilteredBySimplexLink {
|
||||
return chatModel.chats.filter { $0.id == linkChatId }
|
||||
} else {
|
||||
let s = searchString()
|
||||
return s == "" && !showUnreadAndFavorites
|
||||
? chatModel.chats
|
||||
: chatModel.chats.filter { chat in
|
||||
let cInfo = chat.chatInfo
|
||||
switch cInfo {
|
||||
case let .direct(contact):
|
||||
return s == ""
|
||||
? filtered(chat)
|
||||
: (viewNameContains(cInfo, s) ||
|
||||
contact.profile.displayName.localizedLowercase.contains(s) ||
|
||||
contact.fullName.localizedLowercase.contains(s))
|
||||
? filtered(chat)
|
||||
: (viewNameContains(cInfo, s) ||
|
||||
contact.profile.displayName.localizedLowercase.contains(s) ||
|
||||
contact.fullName.localizedLowercase.contains(s))
|
||||
case let .group(gInfo):
|
||||
return s == ""
|
||||
? (filtered(chat) || gInfo.membership.memberStatus == .memInvited)
|
||||
: viewNameContains(cInfo, s)
|
||||
? (filtered(chat) || gInfo.membership.memberStatus == .memInvited)
|
||||
: viewNameContains(cInfo, s)
|
||||
case .local:
|
||||
return s == "" || viewNameContains(cInfo, s)
|
||||
case .contactRequest:
|
||||
return s == "" || viewNameContains(cInfo, s)
|
||||
case let .contactConnection(conn):
|
||||
@@ -238,6 +257,11 @@ struct ChatListView: View {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func searchString() -> String {
|
||||
searchShowingSimplexLink ? "" : searchText.trimmingCharacters(in: .whitespaces).localizedLowercase
|
||||
}
|
||||
|
||||
func filtered(_ chat: Chat) -> Bool {
|
||||
(chat.chatInfo.chatSettings?.favorite ?? false) || chat.chatStats.unreadCount > 0 || chat.chatStats.unreadChat
|
||||
@@ -249,6 +273,121 @@ struct ChatListView: View {
|
||||
}
|
||||
}
|
||||
|
||||
struct ChatListSearchBar: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@Binding var searchMode: Bool
|
||||
@FocusState.Binding var searchFocussed: Bool
|
||||
@Binding var searchText: String
|
||||
@Binding var searchShowingSimplexLink: Bool
|
||||
@Binding var searchChatFilteredBySimplexLink: String?
|
||||
@State private var ignoreSearchTextChange = false
|
||||
@State private var showScanCodeSheet = false
|
||||
@State private var alert: PlanAndConnectAlert?
|
||||
@State private var sheet: PlanAndConnectActionSheet?
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 12) {
|
||||
HStack(spacing: 12) {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "magnifyingglass")
|
||||
TextField("Search or paste SimpleX link", text: $searchText)
|
||||
.foregroundColor(searchShowingSimplexLink ? .secondary : .primary)
|
||||
.disabled(searchShowingSimplexLink)
|
||||
.focused($searchFocussed)
|
||||
.frame(maxWidth: .infinity)
|
||||
if !searchText.isEmpty {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.onTapGesture {
|
||||
searchText = ""
|
||||
}
|
||||
} else if !searchFocussed {
|
||||
HStack(spacing: 24) {
|
||||
if m.pasteboardHasStrings {
|
||||
Image(systemName: "doc")
|
||||
.onTapGesture {
|
||||
if let str = UIPasteboard.general.string {
|
||||
searchText = str
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Image(systemName: "qrcode")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 20, height: 20)
|
||||
.onTapGesture {
|
||||
showScanCodeSheet = true
|
||||
}
|
||||
}
|
||||
.padding(.trailing, 2)
|
||||
}
|
||||
}
|
||||
.padding(EdgeInsets(top: 7, leading: 7, bottom: 7, trailing: 7))
|
||||
.foregroundColor(.secondary)
|
||||
.background(Color(.tertiarySystemFill))
|
||||
.cornerRadius(10.0)
|
||||
|
||||
if searchFocussed {
|
||||
Text("Cancel")
|
||||
.foregroundColor(.accentColor)
|
||||
.onTapGesture {
|
||||
searchText = ""
|
||||
searchFocussed = false
|
||||
}
|
||||
}
|
||||
}
|
||||
Divider()
|
||||
}
|
||||
.sheet(isPresented: $showScanCodeSheet) {
|
||||
NewChatView(selection: .connect, showQRCodeScanner: true)
|
||||
.environment(\EnvironmentValues.refresh as! WritableKeyPath<EnvironmentValues, RefreshAction?>, nil) // fixes .refreshable in ChatListView affecting nested view
|
||||
}
|
||||
.onChange(of: searchFocussed) { sf in
|
||||
withAnimation { searchMode = sf }
|
||||
}
|
||||
.onChange(of: searchText) { t in
|
||||
if ignoreSearchTextChange {
|
||||
ignoreSearchTextChange = false
|
||||
} else {
|
||||
if let link = strHasSingleSimplexLink(t.trimmingCharacters(in: .whitespaces)) { // if SimpleX link is pasted, show connection dialogue
|
||||
searchFocussed = false
|
||||
if case let .simplexLink(linkType, _, smpHosts) = link.format {
|
||||
ignoreSearchTextChange = true
|
||||
searchText = simplexLinkText(linkType, smpHosts)
|
||||
}
|
||||
searchShowingSimplexLink = true
|
||||
searchChatFilteredBySimplexLink = nil
|
||||
connect(link.text)
|
||||
} else {
|
||||
if t != "" { // if some other text is pasted, enter search mode
|
||||
searchFocussed = true
|
||||
}
|
||||
searchShowingSimplexLink = false
|
||||
searchChatFilteredBySimplexLink = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert(item: $alert) { a in
|
||||
planAndConnectAlert(a, dismiss: true, cleanup: { searchText = "" })
|
||||
}
|
||||
.actionSheet(item: $sheet) { s in
|
||||
planAndConnectActionSheet(s, dismiss: true, cleanup: { searchText = "" })
|
||||
}
|
||||
}
|
||||
|
||||
private func connect(_ link: String) {
|
||||
planAndConnect(
|
||||
link,
|
||||
showAlert: { alert = $0 },
|
||||
showActionSheet: { sheet = $0 },
|
||||
dismiss: false,
|
||||
incognito: nil,
|
||||
filterKnownContact: { searchChatFilteredBySimplexLink = $0.id },
|
||||
filterKnownGroup: { searchChatFilteredBySimplexLink = $0.id }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func chatStoppedIcon() -> some View {
|
||||
Button {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
|
||||
@@ -13,6 +13,7 @@ struct ChatPreviewView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@ObservedObject var chat: Chat
|
||||
@Binding var progressByTimeout: Bool
|
||||
@State var deleting: Bool = false
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
var darkGreen = Color(red: 0, green: 0.5, blue: 0)
|
||||
|
||||
@@ -33,7 +34,7 @@ struct ChatPreviewView: View {
|
||||
HStack(alignment: .top) {
|
||||
chatPreviewTitle()
|
||||
Spacer()
|
||||
(cItem?.timestampText ?? formatTimestampText(chat.chatInfo.updatedAt))
|
||||
(cItem?.timestampText ?? formatTimestampText(chat.chatInfo.chatTs))
|
||||
.font(.subheadline)
|
||||
.frame(minWidth: 60, alignment: .trailing)
|
||||
.foregroundColor(.secondary)
|
||||
@@ -55,6 +56,9 @@ struct ChatPreviewView: View {
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
.padding(.bottom, -8)
|
||||
.onChange(of: chatModel.deletedChats.contains(chat.chatInfo.id)) { contains in
|
||||
deleting = contains
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func chatPreviewImageOverlayIcon() -> some View {
|
||||
@@ -87,13 +91,13 @@ struct ChatPreviewView: View {
|
||||
let t = Text(chat.chatInfo.chatViewName).font(.title3).fontWeight(.bold)
|
||||
switch chat.chatInfo {
|
||||
case let .direct(contact):
|
||||
previewTitle(contact.verified == true ? verifiedIcon + t : t)
|
||||
previewTitle(contact.verified == true ? verifiedIcon + t : t).foregroundColor(deleting ? Color.secondary : nil)
|
||||
case let .group(groupInfo):
|
||||
let v = previewTitle(t)
|
||||
switch (groupInfo.membership.memberStatus) {
|
||||
case .memInvited: v.foregroundColor(chat.chatInfo.incognito ? .indigo : .accentColor)
|
||||
case .memInvited: v.foregroundColor(deleting ? .secondary : chat.chatInfo.incognito ? .indigo : .accentColor)
|
||||
case .memAccepted: v.foregroundColor(.secondary)
|
||||
default: v
|
||||
default: if deleting { v.foregroundColor(.secondary) } else { v }
|
||||
}
|
||||
default: previewTitle(t)
|
||||
}
|
||||
@@ -130,9 +134,9 @@ struct ChatPreviewView: View {
|
||||
.foregroundColor(.white)
|
||||
.padding(.horizontal, 4)
|
||||
.frame(minWidth: 18, minHeight: 18)
|
||||
.background(chat.chatInfo.ntfsEnabled ? Color.accentColor : Color.secondary)
|
||||
.background(chat.chatInfo.ntfsEnabled || chat.chatInfo.chatType == .local ? Color.accentColor : Color.secondary)
|
||||
.cornerRadius(10)
|
||||
} else if !chat.chatInfo.ntfsEnabled {
|
||||
} else if !chat.chatInfo.ntfsEnabled && chat.chatInfo.chatType != .local {
|
||||
Image(systemName: "speaker.slash.fill")
|
||||
.foregroundColor(.secondary)
|
||||
} else if chat.chatInfo.chatSettings?.favorite ?? false {
|
||||
@@ -150,7 +154,7 @@ struct ChatPreviewView: View {
|
||||
let msg = draft.message
|
||||
return image("rectangle.and.pencil.and.ellipsis", color: .accentColor)
|
||||
+ attachment()
|
||||
+ messageText(msg, parseSimpleXMarkdown(msg), nil, preview: true)
|
||||
+ messageText(msg, parseSimpleXMarkdown(msg), nil, preview: true, showSecrets: false)
|
||||
|
||||
func image(_ s: String, color: Color = Color(uiColor: .tertiaryLabel)) -> Text {
|
||||
Text(Image(systemName: s)).foregroundColor(color) + Text(" ")
|
||||
@@ -167,9 +171,20 @@ struct ChatPreviewView: View {
|
||||
}
|
||||
|
||||
func chatItemPreview(_ cItem: ChatItem) -> Text {
|
||||
let itemText = cItem.meta.itemDeleted == nil ? cItem.text : NSLocalizedString("marked deleted", comment: "marked deleted chat item preview text")
|
||||
let itemText = cItem.meta.itemDeleted == nil ? cItem.text : markedDeletedText()
|
||||
let itemFormattedText = cItem.meta.itemDeleted == nil ? cItem.formattedText : nil
|
||||
return messageText(itemText, itemFormattedText, cItem.memberDisplayName, icon: attachment(), preview: true)
|
||||
return messageText(itemText, itemFormattedText, cItem.memberDisplayName, icon: attachment(), preview: true, showSecrets: false)
|
||||
|
||||
// same texts are in markedDeletedText in MarkedDeletedItemView, but it returns LocalizedStringKey;
|
||||
// can be refactored into a single function if functions calling these are changed to return same type
|
||||
func markedDeletedText() -> String {
|
||||
switch cItem.meta.itemDeleted {
|
||||
case let .moderated(_, byGroupMember): String.localizedStringWithFormat(NSLocalizedString("moderated by %@", comment: "marked deleted chat item preview text"), byGroupMember.displayName)
|
||||
case .blocked: NSLocalizedString("blocked", comment: "marked deleted chat item preview text")
|
||||
case .blockedByAdmin: NSLocalizedString("blocked by admin", comment: "marked deleted chat item preview text")
|
||||
case .deleted, nil: NSLocalizedString("marked deleted", comment: "marked deleted chat item preview text")
|
||||
}
|
||||
}
|
||||
|
||||
func attachment() -> String? {
|
||||
switch cItem.content.msgContent {
|
||||
|
||||
@@ -164,6 +164,28 @@ struct ContactConnectionInfo: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func shareLinkButton(_ connReqInvitation: String) -> some View {
|
||||
Button {
|
||||
showShareSheet(items: [simplexChatLink(connReqInvitation)])
|
||||
} label: {
|
||||
settingsRow("square.and.arrow.up") {
|
||||
Text("Share 1-time link")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func oneTimeLinkLearnMoreButton() -> some View {
|
||||
NavigationLink {
|
||||
AddContactLearnMore(showTitle: false)
|
||||
.navigationTitle("One-time invitation link")
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
} label: {
|
||||
settingsRow("info.circle") {
|
||||
Text("Learn more")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContactConnectionInfo_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContactConnectionInfo(contactConnection: PendingContactConnection.getSampleData())
|
||||
|
||||
@@ -149,7 +149,7 @@ struct DatabaseErrorView: View {
|
||||
private func runChatSync(confirmMigrations: MigrationConfirmation? = nil) {
|
||||
do {
|
||||
resetChatCtrl()
|
||||
try initializeChat(start: m.v3DBMigration.startChat, dbKey: useKeychain ? nil : dbKey, confirmMigrations: confirmMigrations)
|
||||
try initializeChat(start: m.v3DBMigration.startChat, confirmStart: m.v3DBMigration.startChat && AppChatState.shared.value == .stopped, dbKey: useKeychain ? nil : dbKey, confirmMigrations: confirmMigrations)
|
||||
if let s = m.chatDbStatus {
|
||||
status = s
|
||||
let am = AlertManager.shared
|
||||
|
||||
@@ -484,6 +484,7 @@ func deleteChatAsync() async throws {
|
||||
try await apiDeleteStorage()
|
||||
_ = kcDatabasePassword.remove()
|
||||
storeDBPassphraseGroupDefault.set(true)
|
||||
deleteAppDatabaseAndFiles()
|
||||
}
|
||||
|
||||
struct DatabaseView_Previews: PreviewProvider {
|
||||
|
||||
@@ -10,6 +10,7 @@ import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct ChatInfoImage: View {
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@ObservedObject var chat: Chat
|
||||
var color = Color(uiColor: .tertiarySystemGroupedBackground)
|
||||
|
||||
@@ -18,13 +19,16 @@ struct ChatInfoImage: View {
|
||||
switch chat.chatInfo {
|
||||
case .direct: iconName = "person.crop.circle.fill"
|
||||
case .group: iconName = "person.2.circle.fill"
|
||||
case .local: iconName = "folder.circle.fill"
|
||||
case .contactRequest: iconName = "person.crop.circle.fill"
|
||||
default: iconName = "circle.fill"
|
||||
}
|
||||
let notesColor = colorScheme == .light ? notesChatColorLight : notesChatColorDark
|
||||
let iconColor = if case .local = chat.chatInfo { notesColor } else { color }
|
||||
return ProfileImage(
|
||||
imageStr: chat.chatInfo.image,
|
||||
iconName: iconName,
|
||||
color: color
|
||||
color: iconColor
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,19 +13,28 @@ struct LocalAuthView: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
var authRequest: LocalAuthRequest
|
||||
@State private var password = ""
|
||||
@State private var allowToReact = true
|
||||
|
||||
var body: some View {
|
||||
PasscodeView(passcode: $password, title: authRequest.title ?? "Enter Passcode", reason: authRequest.reason, submitLabel: "Submit") {
|
||||
PasscodeView(passcode: $password, title: authRequest.title ?? "Enter Passcode", reason: authRequest.reason, submitLabel: "Submit",
|
||||
buttonsEnabled: $allowToReact) {
|
||||
if let sdPassword = kcSelfDestructPassword.get(), authRequest.selfDestruct && password == sdPassword {
|
||||
allowToReact = false
|
||||
deleteStorageAndRestart(sdPassword) { r in
|
||||
m.laRequest = nil
|
||||
authRequest.completed(r)
|
||||
}
|
||||
return
|
||||
}
|
||||
let r: LAResult = password == authRequest.password
|
||||
? .success
|
||||
: .failed(authError: NSLocalizedString("Incorrect passcode", comment: "PIN entry"))
|
||||
let r: LAResult
|
||||
if password == authRequest.password {
|
||||
if authRequest.selfDestruct && kcSelfDestructPassword.get() != nil && !m.chatInitialized {
|
||||
initChatAndMigrate()
|
||||
}
|
||||
r = .success
|
||||
} else {
|
||||
r = .failed(authError: NSLocalizedString("Incorrect passcode", comment: "PIN entry"))
|
||||
}
|
||||
m.laRequest = nil
|
||||
authRequest.completed(r)
|
||||
} cancel: {
|
||||
@@ -37,8 +46,27 @@ struct LocalAuthView: View {
|
||||
private func deleteStorageAndRestart(_ password: String, completed: @escaping (LAResult) -> Void) {
|
||||
Task {
|
||||
do {
|
||||
try await stopChatAsync()
|
||||
try await deleteChatAsync()
|
||||
/** Waiting until [initializeChat] finishes */
|
||||
while (m.ctrlInitInProgress) {
|
||||
try await Task.sleep(nanoseconds: 50_000000)
|
||||
}
|
||||
if m.chatRunning == true {
|
||||
try await stopChatAsync()
|
||||
}
|
||||
if m.chatInitialized {
|
||||
/**
|
||||
* The following sequence can bring a user here:
|
||||
* the user opened the app, entered app passcode, went to background, returned back, entered self-destruct code.
|
||||
* In this case database should be closed to prevent possible situation when OS can deny database removal command
|
||||
* */
|
||||
chatCloseStore()
|
||||
}
|
||||
deleteAppDatabaseAndFiles()
|
||||
// Clear sensitive data on screen just in case app fails to hide its views while new database is created
|
||||
m.chatId = nil
|
||||
m.reversedChatItems = []
|
||||
m.chats = []
|
||||
m.users = []
|
||||
_ = kcAppPassword.set(password)
|
||||
_ = kcSelfDestructPassword.remove()
|
||||
await NtfManager.shared.removeAllNotifications()
|
||||
@@ -53,7 +81,7 @@ struct LocalAuthView: View {
|
||||
try initializeChat(start: true)
|
||||
m.chatDbChanged = false
|
||||
AppChatState.shared.set(.active)
|
||||
if m.currentUser != nil { return }
|
||||
if m.currentUser != nil || !m.chatInitialized { return }
|
||||
var profile: Profile? = nil
|
||||
if let displayName = displayName, displayName != "" {
|
||||
profile = Profile(displayName: displayName, fullName: "")
|
||||
|
||||
@@ -14,6 +14,8 @@ struct PasscodeView: View {
|
||||
var reason: String? = nil
|
||||
var submitLabel: LocalizedStringKey
|
||||
var submitEnabled: ((String) -> Bool)?
|
||||
@Binding var buttonsEnabled: Bool
|
||||
|
||||
var submit: () -> Void
|
||||
var cancel: () -> Void
|
||||
|
||||
@@ -70,11 +72,11 @@ struct PasscodeView: View {
|
||||
@ViewBuilder private func buttonsView() -> some View {
|
||||
Button(action: cancel) {
|
||||
Label("Cancel", systemImage: "multiply")
|
||||
}
|
||||
}.disabled(!buttonsEnabled)
|
||||
Button(action: submit) {
|
||||
Label(submitLabel, systemImage: "checkmark")
|
||||
}
|
||||
.disabled(submitEnabled?(passcode) == false || passcode.count < 4)
|
||||
.disabled(submitEnabled?(passcode) == false || passcode.count < 4 || !buttonsEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +87,7 @@ struct PasscodeViewView_Previews: PreviewProvider {
|
||||
title: "Enter Passcode",
|
||||
reason: "Unlock app",
|
||||
submitLabel: "Submit",
|
||||
buttonsEnabled: Binding.constant(true),
|
||||
submit: {},
|
||||
cancel: {}
|
||||
)
|
||||
|
||||
@@ -11,6 +11,7 @@ import SimpleXChat
|
||||
|
||||
struct SetAppPasscodeView: View {
|
||||
var passcodeKeychain: KeyChainItem = kcAppPassword
|
||||
var prohibitedPasscodeKeychain: KeyChainItem = kcSelfDestructPassword
|
||||
var title: LocalizedStringKey = "New Passcode"
|
||||
var reason: String?
|
||||
var submit: () -> Void
|
||||
@@ -41,7 +42,10 @@ struct SetAppPasscodeView: View {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setPasswordView(title: title, submitLabel: "Save") {
|
||||
setPasswordView(title: title,
|
||||
submitLabel: "Save",
|
||||
// Do not allow to set app passcode == selfDestruct passcode
|
||||
submitEnabled: { pwd in pwd != prohibitedPasscodeKeychain.get() }) {
|
||||
enteredPassword = passcode
|
||||
passcode = ""
|
||||
confirming = true
|
||||
@@ -54,7 +58,7 @@ struct SetAppPasscodeView: View {
|
||||
}
|
||||
|
||||
private func setPasswordView(title: LocalizedStringKey, submitLabel: LocalizedStringKey, submitEnabled: (((String) -> Bool))? = nil, submit: @escaping () -> Void) -> some View {
|
||||
PasscodeView(passcode: $passcode, title: title, reason: reason, submitLabel: submitLabel, submitEnabled: submitEnabled, submit: submit) {
|
||||
PasscodeView(passcode: $passcode, title: title, reason: reason, submitLabel: submitLabel, submitEnabled: submitEnabled, buttonsEnabled: Binding.constant(true), submit: submit) {
|
||||
dismiss()
|
||||
cancel()
|
||||
}
|
||||
|
||||
@@ -9,8 +9,20 @@
|
||||
import SwiftUI
|
||||
|
||||
struct AddContactLearnMore: View {
|
||||
var showTitle: Bool
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
if showTitle {
|
||||
Text("One-time invitation link")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(.vertical)
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||
}
|
||||
VStack(alignment: .leading, spacing: 18) {
|
||||
Text("To connect, your contact can scan QR code or use the link in the app.")
|
||||
Text("If you can't meet in person, show QR code in a video call, or share the link.")
|
||||
@@ -23,6 +35,6 @@ struct AddContactLearnMore: View {
|
||||
|
||||
struct AddContactLearnMore_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AddContactLearnMore()
|
||||
AddContactLearnMore(showTitle: true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
//
|
||||
// AddContactView.swift
|
||||
// SimpleX
|
||||
//
|
||||
// Created by Evgeny Poberezkin on 29/01/2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import CoreImage.CIFilterBuiltins
|
||||
import SimpleXChat
|
||||
|
||||
struct AddContactView: View {
|
||||
@EnvironmentObject private var chatModel: ChatModel
|
||||
@Binding var contactConnection: PendingContactConnection?
|
||||
var connReqInvitation: String
|
||||
@AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
List {
|
||||
Section {
|
||||
if connReqInvitation != "" {
|
||||
SimpleXLinkQRCode(uri: connReqInvitation)
|
||||
} else {
|
||||
ProgressView()
|
||||
.progressViewStyle(.circular)
|
||||
.scaleEffect(2)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical)
|
||||
}
|
||||
IncognitoToggle(incognitoEnabled: $incognitoDefault)
|
||||
.disabled(contactConnection == nil)
|
||||
shareLinkButton(connReqInvitation)
|
||||
oneTimeLinkLearnMoreButton()
|
||||
} header: {
|
||||
Text("1-time link")
|
||||
} footer: {
|
||||
sharedProfileInfo(incognitoDefault)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear { chatModel.connReqInv = connReqInvitation }
|
||||
.onChange(of: incognitoDefault) { incognito in
|
||||
Task {
|
||||
do {
|
||||
if let contactConn = contactConnection,
|
||||
let conn = try await apiSetConnectionIncognito(connId: contactConn.pccConnId, incognito: incognito) {
|
||||
await MainActor.run {
|
||||
contactConnection = conn
|
||||
chatModel.updateContactConnection(conn)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
logger.error("apiSetConnectionIncognito error: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct IncognitoToggle: View {
|
||||
@Binding var incognitoEnabled: Bool
|
||||
@State private var showIncognitoSheet = false
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .leading) {
|
||||
Image(systemName: incognitoEnabled ? "theatermasks.fill" : "theatermasks")
|
||||
.frame(maxWidth: 24, maxHeight: 24, alignment: .center)
|
||||
.foregroundColor(incognitoEnabled ? Color.indigo : .secondary)
|
||||
.font(.system(size: 14))
|
||||
Toggle(isOn: $incognitoEnabled) {
|
||||
HStack(spacing: 6) {
|
||||
Text("Incognito")
|
||||
Image(systemName: "info.circle")
|
||||
.foregroundColor(.accentColor)
|
||||
.font(.system(size: 14))
|
||||
}
|
||||
.onTapGesture {
|
||||
showIncognitoSheet = true
|
||||
}
|
||||
}
|
||||
.padding(.leading, 36)
|
||||
}
|
||||
.sheet(isPresented: $showIncognitoSheet) {
|
||||
IncognitoHelp()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sharedProfileInfo(_ incognito: Bool) -> Text {
|
||||
let name = ChatModel.shared.currentUser?.displayName ?? ""
|
||||
return Text(
|
||||
incognito
|
||||
? "A new random profile will be shared."
|
||||
: "Your profile **\(name)** will be shared."
|
||||
)
|
||||
}
|
||||
|
||||
func shareLinkButton(_ connReqInvitation: String) -> some View {
|
||||
Button {
|
||||
showShareSheet(items: [simplexChatLink(connReqInvitation)])
|
||||
} label: {
|
||||
settingsRow("square.and.arrow.up") {
|
||||
Text("Share 1-time link")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func oneTimeLinkLearnMoreButton() -> some View {
|
||||
NavigationLink {
|
||||
AddContactLearnMore()
|
||||
.navigationTitle("One-time invitation link")
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
} label: {
|
||||
settingsRow("info.circle") {
|
||||
Text("Learn more")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AddContactView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AddContactView(
|
||||
contactConnection: Binding.constant(PendingContactConnection.getSampleData()),
|
||||
connReqInvitation: "https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FFe5ICmvrm4wkrr6X1LTMii-lhBqLeB76%23MCowBQYDK2VuAyEAdhZZsHpuaAk3Hh1q0uNb_6hGTpuwBIrsp2z9U2T0oC0%3D&e2e=v%3D1%26x3dh%3DMEIwBQYDK2VvAzkAcz6jJk71InuxA0bOX7OUhddfB8Ov7xwQIlIDeXBRZaOntUU4brU5Y3rBzroZBdQJi0FKdtt_D7I%3D%2CMEIwBQYDK2VvAzkA-hDvk1duBi1hlOr08VWSI-Ou4JNNSQjseY69QyKm7Kgg1zZjbpGfyBqSZ2eqys6xtoV4ZtoQUXQ%3D"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -187,6 +187,7 @@ struct AddGroupView: View {
|
||||
hideKeyboard()
|
||||
do {
|
||||
profile.displayName = profile.displayName.trimmingCharacters(in: .whitespaces)
|
||||
profile.groupPreferences = GroupPreferences(history: GroupPreference(enable: .on))
|
||||
let gInfo = try apiNewGroup(incognito: incognitoDefault, groupProfile: profile)
|
||||
Task {
|
||||
let groupMembers = await apiListMembers(gInfo.groupId)
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
//
|
||||
// ConnectViaLinkView.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by Evgeny on 21/09/2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
enum ConnectViaLinkTab: String {
|
||||
case scan
|
||||
case paste
|
||||
}
|
||||
|
||||
struct ConnectViaLinkView: View {
|
||||
@State private var selection: ConnectViaLinkTab = connectViaLinkTabDefault.get()
|
||||
|
||||
var body: some View {
|
||||
TabView(selection: $selection) {
|
||||
ScanToConnectView()
|
||||
.tabItem {
|
||||
Label("Scan QR code", systemImage: "qrcode")
|
||||
}
|
||||
.tag(ConnectViaLinkTab.scan)
|
||||
PasteToConnectView()
|
||||
.tabItem {
|
||||
Label("Paste received link", systemImage: "doc.plaintext")
|
||||
}
|
||||
.tag(ConnectViaLinkTab.paste)
|
||||
}
|
||||
.onChange(of: selection) { _ in
|
||||
connectViaLinkTabDefault.set(selection)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ConnectViaLinkView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ConnectViaLinkView()
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
//
|
||||
// CreateLinkView.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by Evgeny on 21/09/2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
enum CreateLinkTab {
|
||||
case oneTime
|
||||
case longTerm
|
||||
|
||||
var title: LocalizedStringKey {
|
||||
switch self {
|
||||
case .oneTime: return "One-time invitation link"
|
||||
case .longTerm: return "Your SimpleX address"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CreateLinkView: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@State var selection: CreateLinkTab
|
||||
@State var connReqInvitation: String = ""
|
||||
@State var contactConnection: PendingContactConnection? = nil
|
||||
@State private var creatingConnReq = false
|
||||
var viaNavLink = false
|
||||
|
||||
var body: some View {
|
||||
if viaNavLink {
|
||||
createLinkView()
|
||||
} else {
|
||||
NavigationView {
|
||||
createLinkView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func createLinkView() -> some View {
|
||||
TabView(selection: $selection) {
|
||||
AddContactView(contactConnection: $contactConnection, connReqInvitation: connReqInvitation)
|
||||
.tabItem {
|
||||
Label(
|
||||
connReqInvitation == ""
|
||||
? "Create one-time invitation link"
|
||||
: "One-time invitation link",
|
||||
systemImage: "1.circle"
|
||||
)
|
||||
}
|
||||
.tag(CreateLinkTab.oneTime)
|
||||
UserAddressView(viaCreateLinkView: true)
|
||||
.tabItem {
|
||||
Label("Your SimpleX address", systemImage: "infinity.circle")
|
||||
}
|
||||
.tag(CreateLinkTab.longTerm)
|
||||
}
|
||||
.onChange(of: selection) { _ in
|
||||
if case .oneTime = selection, connReqInvitation == "", contactConnection == nil && !creatingConnReq {
|
||||
createInvitation()
|
||||
}
|
||||
}
|
||||
.onAppear { m.connReqInv = connReqInvitation }
|
||||
.onDisappear { m.connReqInv = nil }
|
||||
.navigationTitle(selection.title)
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
}
|
||||
|
||||
private func createInvitation() {
|
||||
creatingConnReq = true
|
||||
Task {
|
||||
if let (connReq, pcc) = await apiAddContact(incognito: incognitoGroupDefault.get()) {
|
||||
await MainActor.run {
|
||||
m.updateContactConnection(pcc)
|
||||
connReqInvitation = connReq
|
||||
contactConnection = pcc
|
||||
m.connReqInv = connReq
|
||||
}
|
||||
} else {
|
||||
await MainActor.run {
|
||||
creatingConnReq = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CreateLinkView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
CreateLinkView(selection: CreateLinkTab.oneTime)
|
||||
}
|
||||
}
|
||||
@@ -1,466 +0,0 @@
|
||||
//
|
||||
// NewChatButton.swift
|
||||
// SimpleX
|
||||
//
|
||||
// Created by Evgeny Poberezkin on 31/01/2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
enum NewChatAction: Identifiable {
|
||||
case createLink(link: String, connection: PendingContactConnection)
|
||||
case connectViaLink
|
||||
case createGroup
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case let .createLink(link, _): return "createLink \(link)"
|
||||
case .connectViaLink: return "connectViaLink"
|
||||
case .createGroup: return "createGroup"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct NewChatButton: View {
|
||||
@Binding var showAddChat: Bool
|
||||
@State private var actionSheet: NewChatAction?
|
||||
|
||||
var body: some View {
|
||||
Button { showAddChat = true } label: {
|
||||
Image(systemName: "square.and.pencil")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 24, height: 24)
|
||||
}
|
||||
.confirmationDialog("Start a new chat", isPresented: $showAddChat, titleVisibility: .visible) {
|
||||
Button("Share one-time invitation link") { addContactAction() }
|
||||
Button("Connect via link / QR code") { actionSheet = .connectViaLink }
|
||||
Button("Create secret group") { actionSheet = .createGroup }
|
||||
}
|
||||
.sheet(item: $actionSheet) { sheet in
|
||||
switch sheet {
|
||||
case let .createLink(link, pcc):
|
||||
CreateLinkView(selection: .oneTime, connReqInvitation: link, contactConnection: pcc)
|
||||
case .connectViaLink: ConnectViaLinkView()
|
||||
case .createGroup: AddGroupView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addContactAction() {
|
||||
Task {
|
||||
if let (connReq, pcc) = await apiAddContact(incognito: incognitoGroupDefault.get()) {
|
||||
await MainActor.run {
|
||||
ChatModel.shared.updateContactConnection(pcc)
|
||||
}
|
||||
actionSheet = .createLink(link: connReq, connection: pcc)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum PlanAndConnectAlert: Identifiable {
|
||||
case ownInvitationLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool)
|
||||
case invitationLinkConnecting(connectionLink: String)
|
||||
case ownContactAddressConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool)
|
||||
case contactAddressConnectingConfirmReconnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool)
|
||||
case groupLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool)
|
||||
case groupLinkConnectingConfirmReconnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool)
|
||||
case groupLinkConnecting(connectionLink: String, groupInfo: GroupInfo?)
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case let .ownInvitationLinkConfirmConnect(connectionLink, _, _): return "ownInvitationLinkConfirmConnect \(connectionLink)"
|
||||
case let .invitationLinkConnecting(connectionLink): return "invitationLinkConnecting \(connectionLink)"
|
||||
case let .ownContactAddressConfirmConnect(connectionLink, _, _): return "ownContactAddressConfirmConnect \(connectionLink)"
|
||||
case let .contactAddressConnectingConfirmReconnect(connectionLink, _, _): return "contactAddressConnectingConfirmReconnect \(connectionLink)"
|
||||
case let .groupLinkConfirmConnect(connectionLink, _, _): return "groupLinkConfirmConnect \(connectionLink)"
|
||||
case let .groupLinkConnectingConfirmReconnect(connectionLink, _, _): return "groupLinkConnectingConfirmReconnect \(connectionLink)"
|
||||
case let .groupLinkConnecting(connectionLink, _): return "groupLinkConnecting \(connectionLink)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func planAndConnectAlert(_ alert: PlanAndConnectAlert, dismiss: Bool) -> Alert {
|
||||
switch alert {
|
||||
case let .ownInvitationLinkConfirmConnect(connectionLink, connectionPlan, incognito):
|
||||
return Alert(
|
||||
title: Text("Connect to yourself?"),
|
||||
message: Text("This is your own one-time link!"),
|
||||
primaryButton: .destructive(
|
||||
Text(incognito ? "Connect incognito" : "Connect"),
|
||||
action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito) }
|
||||
),
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
case .invitationLinkConnecting:
|
||||
return Alert(
|
||||
title: Text("Already connecting!"),
|
||||
message: Text("You are already connecting via this one-time link!")
|
||||
)
|
||||
case let .ownContactAddressConfirmConnect(connectionLink, connectionPlan, incognito):
|
||||
return Alert(
|
||||
title: Text("Connect to yourself?"),
|
||||
message: Text("This is your own SimpleX address!"),
|
||||
primaryButton: .destructive(
|
||||
Text(incognito ? "Connect incognito" : "Connect"),
|
||||
action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito) }
|
||||
),
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
case let .contactAddressConnectingConfirmReconnect(connectionLink, connectionPlan, incognito):
|
||||
return Alert(
|
||||
title: Text("Repeat connection request?"),
|
||||
message: Text("You have already requested connection via this address!"),
|
||||
primaryButton: .destructive(
|
||||
Text(incognito ? "Connect incognito" : "Connect"),
|
||||
action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito) }
|
||||
),
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
case let .groupLinkConfirmConnect(connectionLink, connectionPlan, incognito):
|
||||
return Alert(
|
||||
title: Text("Join group?"),
|
||||
message: Text("You will connect to all group members."),
|
||||
primaryButton: .default(
|
||||
Text(incognito ? "Join incognito" : "Join"),
|
||||
action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito) }
|
||||
),
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
case let .groupLinkConnectingConfirmReconnect(connectionLink, connectionPlan, incognito):
|
||||
return Alert(
|
||||
title: Text("Repeat join request?"),
|
||||
message: Text("You are already joining the group via this link!"),
|
||||
primaryButton: .destructive(
|
||||
Text(incognito ? "Join incognito" : "Join"),
|
||||
action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito) }
|
||||
),
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
case let .groupLinkConnecting(_, groupInfo):
|
||||
if let groupInfo = groupInfo {
|
||||
return Alert(
|
||||
title: Text("Group already exists!"),
|
||||
message: Text("You are already joining the group \(groupInfo.displayName).")
|
||||
)
|
||||
} else {
|
||||
return Alert(
|
||||
title: Text("Already joining the group!"),
|
||||
message: Text("You are already joining the group via this link.")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum PlanAndConnectActionSheet: Identifiable {
|
||||
case askCurrentOrIncognitoProfile(connectionLink: String, connectionPlan: ConnectionPlan?, title: LocalizedStringKey)
|
||||
case askCurrentOrIncognitoProfileDestructive(connectionLink: String, connectionPlan: ConnectionPlan, title: LocalizedStringKey)
|
||||
case askCurrentOrIncognitoProfileConnectContactViaAddress(contact: Contact)
|
||||
case ownGroupLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool?, groupInfo: GroupInfo)
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case let .askCurrentOrIncognitoProfile(connectionLink, _, _): return "askCurrentOrIncognitoProfile \(connectionLink)"
|
||||
case let .askCurrentOrIncognitoProfileDestructive(connectionLink, _, _): return "askCurrentOrIncognitoProfileDestructive \(connectionLink)"
|
||||
case let .askCurrentOrIncognitoProfileConnectContactViaAddress(contact): return "askCurrentOrIncognitoProfileConnectContactViaAddress \(contact.contactId)"
|
||||
case let .ownGroupLinkConfirmConnect(connectionLink, _, _, _): return "ownGroupLinkConfirmConnect \(connectionLink)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func planAndConnectActionSheet(_ sheet: PlanAndConnectActionSheet, dismiss: Bool) -> ActionSheet {
|
||||
switch sheet {
|
||||
case let .askCurrentOrIncognitoProfile(connectionLink, connectionPlan, title):
|
||||
return ActionSheet(
|
||||
title: Text(title),
|
||||
buttons: [
|
||||
.default(Text("Use current profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: false) },
|
||||
.default(Text("Use new incognito profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: true) },
|
||||
.cancel()
|
||||
]
|
||||
)
|
||||
case let .askCurrentOrIncognitoProfileDestructive(connectionLink, connectionPlan, title):
|
||||
return ActionSheet(
|
||||
title: Text(title),
|
||||
buttons: [
|
||||
.destructive(Text("Use current profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: false) },
|
||||
.destructive(Text("Use new incognito profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: true) },
|
||||
.cancel()
|
||||
]
|
||||
)
|
||||
case let .askCurrentOrIncognitoProfileConnectContactViaAddress(contact):
|
||||
return ActionSheet(
|
||||
title: Text("Connect with \(contact.chatViewName)"),
|
||||
buttons: [
|
||||
.default(Text("Use current profile")) { connectContactViaAddress_(contact, dismiss: dismiss, incognito: false) },
|
||||
.default(Text("Use new incognito profile")) { connectContactViaAddress_(contact, dismiss: dismiss, incognito: true) },
|
||||
.cancel()
|
||||
]
|
||||
)
|
||||
case let .ownGroupLinkConfirmConnect(connectionLink, connectionPlan, incognito, groupInfo):
|
||||
if let incognito = incognito {
|
||||
return ActionSheet(
|
||||
title: Text("Join your group?\nThis is your link for group \(groupInfo.displayName)!"),
|
||||
buttons: [
|
||||
.default(Text("Open group")) { openKnownGroup(groupInfo, dismiss: dismiss, showAlreadyExistsAlert: nil) },
|
||||
.destructive(Text(incognito ? "Join incognito" : "Join with current profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito) },
|
||||
.cancel()
|
||||
]
|
||||
)
|
||||
} else {
|
||||
return ActionSheet(
|
||||
title: Text("Join your group?\nThis is your link for group \(groupInfo.displayName)!"),
|
||||
buttons: [
|
||||
.default(Text("Open group")) { openKnownGroup(groupInfo, dismiss: dismiss, showAlreadyExistsAlert: nil) },
|
||||
.destructive(Text("Use current profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: false) },
|
||||
.destructive(Text("Use new incognito profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: true) },
|
||||
.cancel()
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func planAndConnect(
|
||||
_ connectionLink: String,
|
||||
showAlert: @escaping (PlanAndConnectAlert) -> Void,
|
||||
showActionSheet: @escaping (PlanAndConnectActionSheet) -> Void,
|
||||
dismiss: Bool,
|
||||
incognito: Bool?
|
||||
) {
|
||||
Task {
|
||||
do {
|
||||
let connectionPlan = try await apiConnectPlan(connReq: connectionLink)
|
||||
switch connectionPlan {
|
||||
case let .invitationLink(ilp):
|
||||
switch ilp {
|
||||
case .ok:
|
||||
logger.debug("planAndConnect, .invitationLink, .ok, incognito=\(incognito?.description ?? "nil")")
|
||||
if let incognito = incognito {
|
||||
connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito)
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect via one-time link"))
|
||||
}
|
||||
case .ownLink:
|
||||
logger.debug("planAndConnect, .invitationLink, .ownLink, incognito=\(incognito?.description ?? "nil")")
|
||||
if let incognito = incognito {
|
||||
showAlert(.ownInvitationLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own one-time link!"))
|
||||
}
|
||||
case let .connecting(contact_):
|
||||
logger.debug("planAndConnect, .invitationLink, .connecting, incognito=\(incognito?.description ?? "nil")")
|
||||
if let contact = contact_ {
|
||||
openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyConnectingAlert(contact)) }
|
||||
} else {
|
||||
showAlert(.invitationLinkConnecting(connectionLink: connectionLink))
|
||||
}
|
||||
case let .known(contact):
|
||||
logger.debug("planAndConnect, .invitationLink, .known, incognito=\(incognito?.description ?? "nil")")
|
||||
openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyExistsAlert(contact)) }
|
||||
}
|
||||
case let .contactAddress(cap):
|
||||
switch cap {
|
||||
case .ok:
|
||||
logger.debug("planAndConnect, .contactAddress, .ok, incognito=\(incognito?.description ?? "nil")")
|
||||
if let incognito = incognito {
|
||||
connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito)
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect via contact address"))
|
||||
}
|
||||
case .ownLink:
|
||||
logger.debug("planAndConnect, .contactAddress, .ownLink, incognito=\(incognito?.description ?? "nil")")
|
||||
if let incognito = incognito {
|
||||
showAlert(.ownContactAddressConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own SimpleX address!"))
|
||||
}
|
||||
case .connectingConfirmReconnect:
|
||||
logger.debug("planAndConnect, .contactAddress, .connectingConfirmReconnect, incognito=\(incognito?.description ?? "nil")")
|
||||
if let incognito = incognito {
|
||||
showAlert(.contactAddressConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You have already requested connection!\nRepeat connection request?"))
|
||||
}
|
||||
case let .connectingProhibit(contact):
|
||||
logger.debug("planAndConnect, .contactAddress, .connectingProhibit, incognito=\(incognito?.description ?? "nil")")
|
||||
openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyConnectingAlert(contact)) }
|
||||
case let .known(contact):
|
||||
logger.debug("planAndConnect, .contactAddress, .known, incognito=\(incognito?.description ?? "nil")")
|
||||
openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyExistsAlert(contact)) }
|
||||
case let .contactViaAddress(contact):
|
||||
logger.debug("planAndConnect, .contactAddress, .contactViaAddress, incognito=\(incognito?.description ?? "nil")")
|
||||
if let incognito = incognito {
|
||||
connectContactViaAddress_(contact, dismiss: dismiss, incognito: incognito)
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfileConnectContactViaAddress(contact: contact))
|
||||
}
|
||||
}
|
||||
case let .groupLink(glp):
|
||||
switch glp {
|
||||
case .ok:
|
||||
if let incognito = incognito {
|
||||
showAlert(.groupLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Join group"))
|
||||
}
|
||||
case let .ownLink(groupInfo):
|
||||
logger.debug("planAndConnect, .groupLink, .ownLink, incognito=\(incognito?.description ?? "nil")")
|
||||
showActionSheet(.ownGroupLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito, groupInfo: groupInfo))
|
||||
case .connectingConfirmReconnect:
|
||||
logger.debug("planAndConnect, .groupLink, .connectingConfirmReconnect, incognito=\(incognito?.description ?? "nil")")
|
||||
if let incognito = incognito {
|
||||
showAlert(.groupLinkConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You are already joining the group!\nRepeat join request?"))
|
||||
}
|
||||
case let .connectingProhibit(groupInfo_):
|
||||
logger.debug("planAndConnect, .groupLink, .connectingProhibit, incognito=\(incognito?.description ?? "nil")")
|
||||
showAlert(.groupLinkConnecting(connectionLink: connectionLink, groupInfo: groupInfo_))
|
||||
case let .known(groupInfo):
|
||||
logger.debug("planAndConnect, .groupLink, .known, incognito=\(incognito?.description ?? "nil")")
|
||||
openKnownGroup(groupInfo, dismiss: dismiss) { AlertManager.shared.showAlert(groupAlreadyExistsAlert(groupInfo)) }
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
logger.debug("planAndConnect, plan error")
|
||||
if let incognito = incognito {
|
||||
connectViaLink(connectionLink, connectionPlan: nil, dismiss: dismiss, incognito: incognito)
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: nil, title: "Connect via link"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func connectContactViaAddress_(_ contact: Contact, dismiss: Bool, incognito: Bool) {
|
||||
Task {
|
||||
if dismiss {
|
||||
DispatchQueue.main.async {
|
||||
dismissAllSheets(animated: true)
|
||||
}
|
||||
}
|
||||
_ = await connectContactViaAddress(contact.contactId, incognito)
|
||||
}
|
||||
}
|
||||
|
||||
private func connectViaLink(_ connectionLink: String, connectionPlan: ConnectionPlan?, dismiss: Bool, incognito: Bool) {
|
||||
Task {
|
||||
if let (connReqType, pcc) = await apiConnect(incognito: incognito, connReq: connectionLink) {
|
||||
await MainActor.run {
|
||||
ChatModel.shared.updateContactConnection(pcc)
|
||||
}
|
||||
let crt: ConnReqType
|
||||
if let plan = connectionPlan {
|
||||
crt = planToConnReqType(plan)
|
||||
} else {
|
||||
crt = connReqType
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
if dismiss {
|
||||
dismissAllSheets(animated: true) {
|
||||
AlertManager.shared.showAlert(connReqSentAlert(crt))
|
||||
}
|
||||
} else {
|
||||
AlertManager.shared.showAlert(connReqSentAlert(crt))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if dismiss {
|
||||
DispatchQueue.main.async {
|
||||
dismissAllSheets(animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func openKnownContact(_ contact: Contact, dismiss: Bool, showAlreadyExistsAlert: (() -> Void)?) {
|
||||
Task {
|
||||
let m = ChatModel.shared
|
||||
if let c = m.getContactChat(contact.contactId) {
|
||||
DispatchQueue.main.async {
|
||||
if dismiss {
|
||||
dismissAllSheets(animated: true) {
|
||||
m.chatId = c.id
|
||||
showAlreadyExistsAlert?()
|
||||
}
|
||||
} else {
|
||||
m.chatId = c.id
|
||||
showAlreadyExistsAlert?()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func openKnownGroup(_ groupInfo: GroupInfo, dismiss: Bool, showAlreadyExistsAlert: (() -> Void)?) {
|
||||
Task {
|
||||
let m = ChatModel.shared
|
||||
if let g = m.getGroupChat(groupInfo.groupId) {
|
||||
DispatchQueue.main.async {
|
||||
if dismiss {
|
||||
dismissAllSheets(animated: true) {
|
||||
m.chatId = g.id
|
||||
showAlreadyExistsAlert?()
|
||||
}
|
||||
} else {
|
||||
m.chatId = g.id
|
||||
showAlreadyExistsAlert?()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func contactAlreadyConnectingAlert(_ contact: Contact) -> Alert {
|
||||
mkAlert(
|
||||
title: "Contact already exists",
|
||||
message: "You are already connecting to \(contact.displayName)."
|
||||
)
|
||||
}
|
||||
|
||||
func groupAlreadyExistsAlert(_ groupInfo: GroupInfo) -> Alert {
|
||||
mkAlert(
|
||||
title: "Group already exists",
|
||||
message: "You are already in group \(groupInfo.displayName)."
|
||||
)
|
||||
}
|
||||
|
||||
enum ConnReqType: Equatable {
|
||||
case invitation
|
||||
case contact
|
||||
case groupLink
|
||||
|
||||
var connReqSentText: LocalizedStringKey {
|
||||
switch self {
|
||||
case .invitation: return "You will be connected when your contact's device is online, please wait or check later!"
|
||||
case .contact: return "You will be connected when your connection request is accepted, please wait or check later!"
|
||||
case .groupLink: return "You will be connected when group link host's device is online, please wait or check later!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func planToConnReqType(_ connectionPlan: ConnectionPlan) -> ConnReqType {
|
||||
switch connectionPlan {
|
||||
case .invitationLink: return .invitation
|
||||
case .contactAddress: return .contact
|
||||
case .groupLink: return .groupLink
|
||||
}
|
||||
}
|
||||
|
||||
func connReqSentAlert(_ type: ConnReqType) -> Alert {
|
||||
return mkAlert(
|
||||
title: "Connection request sent!",
|
||||
message: type.connReqSentText
|
||||
)
|
||||
}
|
||||
|
||||
struct NewChatButton_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NewChatButton(showAddChat: Binding.constant(false))
|
||||
}
|
||||
}
|
||||
52
apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift
Normal file
52
apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift
Normal file
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// NewChatMenuButton.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by spaced4ndy on 28.11.2023.
|
||||
// Copyright © 2023 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
enum NewChatMenuOption: Identifiable {
|
||||
case newContact
|
||||
case newGroup
|
||||
|
||||
var id: Self { self }
|
||||
}
|
||||
|
||||
struct NewChatMenuButton: View {
|
||||
@Binding var newChatMenuOption: NewChatMenuOption?
|
||||
|
||||
var body: some View {
|
||||
Menu {
|
||||
Button {
|
||||
newChatMenuOption = .newContact
|
||||
} label: {
|
||||
Text("Add contact")
|
||||
}
|
||||
Button {
|
||||
newChatMenuOption = .newGroup
|
||||
} label: {
|
||||
Text("Create group")
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "square.and.pencil")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 24, height: 24)
|
||||
}
|
||||
.sheet(item: $newChatMenuOption) { opt in
|
||||
switch opt {
|
||||
case .newContact: NewChatView(selection: .invite)
|
||||
case .newGroup: AddGroupView()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
NewChatMenuButton(
|
||||
newChatMenuOption: Binding.constant(nil)
|
||||
)
|
||||
}
|
||||
959
apps/ios/Shared/Views/NewChat/NewChatView.swift
Normal file
959
apps/ios/Shared/Views/NewChat/NewChatView.swift
Normal file
@@ -0,0 +1,959 @@
|
||||
//
|
||||
// NewChatView.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by spaced4ndy on 28.11.2023.
|
||||
// Copyright © 2023 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
import CodeScanner
|
||||
import AVFoundation
|
||||
|
||||
enum SomeAlert: Identifiable {
|
||||
case someAlert(alert: Alert, id: String)
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case let .someAlert(_, id): return id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum NewChatViewAlert: Identifiable {
|
||||
case planAndConnectAlert(alert: PlanAndConnectAlert)
|
||||
case newChatSomeAlert(alert: SomeAlert)
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case let .planAndConnectAlert(alert): return "planAndConnectAlert \(alert.id)"
|
||||
case let .newChatSomeAlert(alert): return "newChatSomeAlert \(alert.id)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum NewChatOption: Identifiable {
|
||||
case invite
|
||||
case connect
|
||||
|
||||
var id: Self { self }
|
||||
}
|
||||
|
||||
struct NewChatView: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@State var selection: NewChatOption
|
||||
@State var showQRCodeScanner = false
|
||||
@State private var invitationUsed: Bool = false
|
||||
@State private var contactConnection: PendingContactConnection? = nil
|
||||
@State private var connReqInvitation: String = ""
|
||||
@State private var creatingConnReq = false
|
||||
@State private var pastedLink: String = ""
|
||||
@State private var alert: NewChatViewAlert?
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Text("New chat")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Spacer()
|
||||
InfoSheetButton {
|
||||
AddContactLearnMore(showTitle: true)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.padding(.top)
|
||||
|
||||
Picker("New chat", selection: $selection) {
|
||||
Label("Add contact", systemImage: "link")
|
||||
.tag(NewChatOption.invite)
|
||||
Label("Connect via link", systemImage: "qrcode")
|
||||
.tag(NewChatOption.connect)
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
.padding()
|
||||
|
||||
VStack {
|
||||
// it seems there's a bug in iOS 15 if several views in switch (or if-else) statement have different transitions
|
||||
// https://developer.apple.com/forums/thread/714977?answerId=731615022#731615022
|
||||
if case .invite = selection {
|
||||
prepareAndInviteView()
|
||||
.transition(.move(edge: .leading))
|
||||
.onAppear {
|
||||
createInvitation()
|
||||
}
|
||||
}
|
||||
if case .connect = selection {
|
||||
ConnectView(showQRCodeScanner: showQRCodeScanner, pastedLink: $pastedLink, alert: $alert)
|
||||
.transition(.move(edge: .trailing))
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(
|
||||
// Rectangle is needed for swipe gesture to work on mostly empty views (creatingLinkProgressView and retryButton)
|
||||
Rectangle()
|
||||
.fill(Color(uiColor: .systemGroupedBackground))
|
||||
)
|
||||
.animation(.easeInOut(duration: 0.3333), value: selection)
|
||||
.gesture(DragGesture(minimumDistance: 20.0, coordinateSpace: .local)
|
||||
.onChanged { value in
|
||||
switch(value.translation.width, value.translation.height) {
|
||||
case (...0, -30...30): // left swipe
|
||||
if selection == .invite {
|
||||
selection = .connect
|
||||
}
|
||||
case (0..., -30...30): // right swipe
|
||||
if selection == .connect {
|
||||
selection = .invite
|
||||
}
|
||||
default: ()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
.background(Color(.systemGroupedBackground))
|
||||
.onChange(of: invitationUsed) { used in
|
||||
if used && !(m.showingInvitation?.connChatUsed ?? true) {
|
||||
m.markShowingInvitationUsed()
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
if !(m.showingInvitation?.connChatUsed ?? true),
|
||||
let conn = contactConnection {
|
||||
AlertManager.shared.showAlert(Alert(
|
||||
title: Text("Keep unused invitation?"),
|
||||
message: Text("You can view invitation link again in connection details."),
|
||||
primaryButton: .default(Text("Keep")) {},
|
||||
secondaryButton: .destructive(Text("Delete")) {
|
||||
Task {
|
||||
await deleteChat(Chat(
|
||||
chatInfo: .contactConnection(contactConnection: conn),
|
||||
chatItems: []
|
||||
))
|
||||
}
|
||||
}
|
||||
))
|
||||
}
|
||||
m.showingInvitation = nil
|
||||
}
|
||||
.alert(item: $alert) { a in
|
||||
switch(a) {
|
||||
case let .planAndConnectAlert(alert):
|
||||
return planAndConnectAlert(alert, dismiss: true, cleanup: { pastedLink = "" })
|
||||
case let .newChatSomeAlert(.someAlert(alert, _)):
|
||||
return alert
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func prepareAndInviteView() -> some View {
|
||||
ZStack { // ZStack is needed for views to not make transitions between each other
|
||||
if connReqInvitation != "" {
|
||||
InviteView(
|
||||
invitationUsed: $invitationUsed,
|
||||
contactConnection: $contactConnection,
|
||||
connReqInvitation: connReqInvitation
|
||||
)
|
||||
} else if creatingConnReq {
|
||||
creatingLinkProgressView()
|
||||
} else {
|
||||
retryButton()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func createInvitation() {
|
||||
if connReqInvitation == "" && contactConnection == nil && !creatingConnReq {
|
||||
creatingConnReq = true
|
||||
Task {
|
||||
_ = try? await Task.sleep(nanoseconds: 250_000000)
|
||||
let (r, apiAlert) = await apiAddContact(incognito: incognitoGroupDefault.get())
|
||||
if let (connReq, pcc) = r {
|
||||
await MainActor.run {
|
||||
m.updateContactConnection(pcc)
|
||||
m.showingInvitation = ShowingInvitation(connId: pcc.id, connChatUsed: false)
|
||||
connReqInvitation = connReq
|
||||
contactConnection = pcc
|
||||
}
|
||||
} else {
|
||||
await MainActor.run {
|
||||
creatingConnReq = false
|
||||
if let apiAlert = apiAlert {
|
||||
alert = .newChatSomeAlert(alert: .someAlert(alert: apiAlert, id: "createInvitation error"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rectangle here and in retryButton are needed for gesture to work
|
||||
private func creatingLinkProgressView() -> some View {
|
||||
ProgressView("Creating link…")
|
||||
.progressViewStyle(.circular)
|
||||
}
|
||||
|
||||
private func retryButton() -> some View {
|
||||
Button(action: createInvitation) {
|
||||
VStack(spacing: 6) {
|
||||
Image(systemName: "arrow.counterclockwise")
|
||||
Text("Retry")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct InviteView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@Binding var invitationUsed: Bool
|
||||
@Binding var contactConnection: PendingContactConnection?
|
||||
var connReqInvitation: String
|
||||
@AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Section("Share this 1-time invite link") {
|
||||
shareLinkView()
|
||||
}
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 10))
|
||||
|
||||
qrCodeView()
|
||||
|
||||
Section {
|
||||
IncognitoToggle(incognitoEnabled: $incognitoDefault)
|
||||
} footer: {
|
||||
sharedProfileInfo(incognitoDefault)
|
||||
}
|
||||
}
|
||||
.onChange(of: incognitoDefault) { incognito in
|
||||
Task {
|
||||
do {
|
||||
if let contactConn = contactConnection,
|
||||
let conn = try await apiSetConnectionIncognito(connId: contactConn.pccConnId, incognito: incognito) {
|
||||
await MainActor.run {
|
||||
contactConnection = conn
|
||||
chatModel.updateContactConnection(conn)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
logger.error("apiSetConnectionIncognito error: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
setInvitationUsed()
|
||||
}
|
||||
}
|
||||
|
||||
private func shareLinkView() -> some View {
|
||||
HStack {
|
||||
let link = simplexChatLink(connReqInvitation)
|
||||
linkTextView(link)
|
||||
Button {
|
||||
showShareSheet(items: [link])
|
||||
setInvitationUsed()
|
||||
} label: {
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
.padding(.top, -7)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
|
||||
private func qrCodeView() -> some View {
|
||||
Section("Or show this code") {
|
||||
SimpleXLinkQRCode(uri: connReqInvitation, onShare: setInvitationUsed)
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
||||
.fill(Color(uiColor: .secondarySystemGroupedBackground))
|
||||
)
|
||||
.padding(.horizontal)
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||
}
|
||||
}
|
||||
|
||||
private func setInvitationUsed() {
|
||||
if !invitationUsed {
|
||||
invitationUsed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct ConnectView: View {
|
||||
@Environment(\.dismiss) var dismiss: DismissAction
|
||||
@State var showQRCodeScanner = false
|
||||
@State private var cameraAuthorizationStatus: AVAuthorizationStatus?
|
||||
@Binding var pastedLink: String
|
||||
@Binding var alert: NewChatViewAlert?
|
||||
@State private var sheet: PlanAndConnectActionSheet?
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Section("Paste the link you received") {
|
||||
pasteLinkView()
|
||||
}
|
||||
|
||||
scanCodeView()
|
||||
}
|
||||
.actionSheet(item: $sheet) { s in
|
||||
planAndConnectActionSheet(s, dismiss: true, cleanup: { pastedLink = "" })
|
||||
}
|
||||
.onAppear {
|
||||
let status = AVCaptureDevice.authorizationStatus(for: .video)
|
||||
cameraAuthorizationStatus = status
|
||||
if showQRCodeScanner {
|
||||
switch status {
|
||||
case .notDetermined: askCameraAuthorization()
|
||||
case .restricted: showQRCodeScanner = false
|
||||
case .denied: showQRCodeScanner = false
|
||||
case .authorized: ()
|
||||
@unknown default: askCameraAuthorization()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func askCameraAuthorization(_ cb: (() -> Void)? = nil) {
|
||||
AVCaptureDevice.requestAccess(for: .video) { allowed in
|
||||
cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: .video)
|
||||
if allowed { cb?() }
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func pasteLinkView() -> some View {
|
||||
if pastedLink == "" {
|
||||
Button {
|
||||
if let str = UIPasteboard.general.string {
|
||||
if let link = strHasSingleSimplexLink(str.trimmingCharacters(in: .whitespaces)) {
|
||||
pastedLink = link.text
|
||||
// It would be good to hide it, but right now it is not clear how to release camera in CodeScanner
|
||||
// https://github.com/twostraws/CodeScanner/issues/121
|
||||
// No known tricks worked (changing view ID, wrapping it in another view, etc.)
|
||||
// showQRCodeScanner = false
|
||||
connect(pastedLink)
|
||||
} else {
|
||||
alert = .newChatSomeAlert(alert: .someAlert(
|
||||
alert: mkAlert(title: "Invalid link", message: "The text you pasted is not a SimpleX link."),
|
||||
id: "pasteLinkView: code is not a SimpleX link"
|
||||
))
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Text("Tap to paste link")
|
||||
}
|
||||
.disabled(!ChatModel.shared.pasteboardHasStrings)
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
} else {
|
||||
linkTextView(pastedLink)
|
||||
}
|
||||
}
|
||||
|
||||
private func scanCodeView() -> some View {
|
||||
Section("Or scan QR code") {
|
||||
if showQRCodeScanner, case .authorized = cameraAuthorizationStatus {
|
||||
CodeScannerView(codeTypes: [.qr], scanMode: .continuous, completion: processQRCode)
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.cornerRadius(12)
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||
.padding(.horizontal)
|
||||
} else {
|
||||
Button {
|
||||
switch cameraAuthorizationStatus {
|
||||
case .notDetermined: askCameraAuthorization { showQRCodeScanner = true }
|
||||
case .restricted: ()
|
||||
case .denied: UIApplication.shared.open(appSettingsURL)
|
||||
case .authorized: showQRCodeScanner = true
|
||||
default: askCameraAuthorization { showQRCodeScanner = true }
|
||||
}
|
||||
} label: {
|
||||
ZStack {
|
||||
Rectangle()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.foregroundColor(Color.clear)
|
||||
switch cameraAuthorizationStatus {
|
||||
case .restricted: Text("Camera not available")
|
||||
case .denied: Label("Enable camera access", systemImage: "camera")
|
||||
default: Label("Tap to scan", systemImage: "qrcode")
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
||||
.fill(Color(uiColor: .secondarySystemGroupedBackground))
|
||||
)
|
||||
.padding(.horizontal)
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||
.disabled(cameraAuthorizationStatus == .restricted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func processQRCode(_ resp: Result<ScanResult, ScanError>) {
|
||||
switch resp {
|
||||
case let .success(r):
|
||||
let link = r.string
|
||||
if strIsSimplexLink(r.string) {
|
||||
connect(link)
|
||||
} else {
|
||||
alert = .newChatSomeAlert(alert: .someAlert(
|
||||
alert: mkAlert(title: "Invalid QR code", message: "The code you scanned is not a SimpleX link QR code."),
|
||||
id: "processQRCode: code is not a SimpleX link"
|
||||
))
|
||||
}
|
||||
case let .failure(e):
|
||||
logger.error("processQRCode QR code error: \(e.localizedDescription)")
|
||||
alert = .newChatSomeAlert(alert: .someAlert(
|
||||
alert: mkAlert(title: "Invalid QR code", message: "Error scanning code: \(e.localizedDescription)"),
|
||||
id: "processQRCode: failure"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
private func connect(_ link: String) {
|
||||
planAndConnect(
|
||||
link,
|
||||
showAlert: { alert = .planAndConnectAlert(alert: $0) },
|
||||
showActionSheet: { sheet = $0 },
|
||||
dismiss: true,
|
||||
incognito: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func linkTextView(_ link: String) -> some View {
|
||||
Text(link)
|
||||
.lineLimit(1)
|
||||
.font(.caption)
|
||||
.truncationMode(.middle)
|
||||
}
|
||||
|
||||
struct InfoSheetButton<Content: View>: View {
|
||||
@ViewBuilder let content: Content
|
||||
@State private var showInfoSheet = false
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
showInfoSheet = true
|
||||
} label: {
|
||||
Image(systemName: "info.circle")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 24, height: 24)
|
||||
}
|
||||
.sheet(isPresented: $showInfoSheet) {
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func strIsSimplexLink(_ str: String) -> Bool {
|
||||
if let parsedMd = parseSimpleXMarkdown(str),
|
||||
parsedMd.count == 1,
|
||||
case .simplexLink = parsedMd[0].format {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func strHasSingleSimplexLink(_ str: String) -> FormattedText? {
|
||||
if let parsedMd = parseSimpleXMarkdown(str) {
|
||||
let parsedLinks = parsedMd.filter({ $0.format?.isSimplexLink ?? false })
|
||||
if parsedLinks.count == 1 {
|
||||
return parsedLinks[0]
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
struct IncognitoToggle: View {
|
||||
@Binding var incognitoEnabled: Bool
|
||||
@State private var showIncognitoSheet = false
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .leading) {
|
||||
Image(systemName: incognitoEnabled ? "theatermasks.fill" : "theatermasks")
|
||||
.frame(maxWidth: 24, maxHeight: 24, alignment: .center)
|
||||
.foregroundColor(incognitoEnabled ? Color.indigo : .secondary)
|
||||
.font(.system(size: 14))
|
||||
Toggle(isOn: $incognitoEnabled) {
|
||||
HStack(spacing: 6) {
|
||||
Text("Incognito")
|
||||
Image(systemName: "info.circle")
|
||||
.foregroundColor(.accentColor)
|
||||
.font(.system(size: 14))
|
||||
}
|
||||
.onTapGesture {
|
||||
showIncognitoSheet = true
|
||||
}
|
||||
}
|
||||
.padding(.leading, 36)
|
||||
}
|
||||
.sheet(isPresented: $showIncognitoSheet) {
|
||||
IncognitoHelp()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sharedProfileInfo(_ incognito: Bool) -> Text {
|
||||
let name = ChatModel.shared.currentUser?.displayName ?? ""
|
||||
return Text(
|
||||
incognito
|
||||
? "A new random profile will be shared."
|
||||
: "Your profile **\(name)** will be shared."
|
||||
)
|
||||
}
|
||||
|
||||
enum PlanAndConnectAlert: Identifiable {
|
||||
case ownInvitationLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool)
|
||||
case invitationLinkConnecting(connectionLink: String)
|
||||
case ownContactAddressConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool)
|
||||
case contactAddressConnectingConfirmReconnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool)
|
||||
case groupLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool)
|
||||
case groupLinkConnectingConfirmReconnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool)
|
||||
case groupLinkConnecting(connectionLink: String, groupInfo: GroupInfo?)
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case let .ownInvitationLinkConfirmConnect(connectionLink, _, _): return "ownInvitationLinkConfirmConnect \(connectionLink)"
|
||||
case let .invitationLinkConnecting(connectionLink): return "invitationLinkConnecting \(connectionLink)"
|
||||
case let .ownContactAddressConfirmConnect(connectionLink, _, _): return "ownContactAddressConfirmConnect \(connectionLink)"
|
||||
case let .contactAddressConnectingConfirmReconnect(connectionLink, _, _): return "contactAddressConnectingConfirmReconnect \(connectionLink)"
|
||||
case let .groupLinkConfirmConnect(connectionLink, _, _): return "groupLinkConfirmConnect \(connectionLink)"
|
||||
case let .groupLinkConnectingConfirmReconnect(connectionLink, _, _): return "groupLinkConnectingConfirmReconnect \(connectionLink)"
|
||||
case let .groupLinkConnecting(connectionLink, _): return "groupLinkConnecting \(connectionLink)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func planAndConnectAlert(_ alert: PlanAndConnectAlert, dismiss: Bool, cleanup: (() -> Void)? = nil) -> Alert {
|
||||
switch alert {
|
||||
case let .ownInvitationLinkConfirmConnect(connectionLink, connectionPlan, incognito):
|
||||
return Alert(
|
||||
title: Text("Connect to yourself?"),
|
||||
message: Text("This is your own one-time link!"),
|
||||
primaryButton: .destructive(
|
||||
Text(incognito ? "Connect incognito" : "Connect"),
|
||||
action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup) }
|
||||
),
|
||||
secondaryButton: .cancel() { cleanup?() }
|
||||
)
|
||||
case .invitationLinkConnecting:
|
||||
return Alert(
|
||||
title: Text("Already connecting!"),
|
||||
message: Text("You are already connecting via this one-time link!"),
|
||||
dismissButton: .default(Text("OK")) { cleanup?() }
|
||||
)
|
||||
case let .ownContactAddressConfirmConnect(connectionLink, connectionPlan, incognito):
|
||||
return Alert(
|
||||
title: Text("Connect to yourself?"),
|
||||
message: Text("This is your own SimpleX address!"),
|
||||
primaryButton: .destructive(
|
||||
Text(incognito ? "Connect incognito" : "Connect"),
|
||||
action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup) }
|
||||
),
|
||||
secondaryButton: .cancel() { cleanup?() }
|
||||
)
|
||||
case let .contactAddressConnectingConfirmReconnect(connectionLink, connectionPlan, incognito):
|
||||
return Alert(
|
||||
title: Text("Repeat connection request?"),
|
||||
message: Text("You have already requested connection via this address!"),
|
||||
primaryButton: .destructive(
|
||||
Text(incognito ? "Connect incognito" : "Connect"),
|
||||
action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup) }
|
||||
),
|
||||
secondaryButton: .cancel() { cleanup?() }
|
||||
)
|
||||
case let .groupLinkConfirmConnect(connectionLink, connectionPlan, incognito):
|
||||
return Alert(
|
||||
title: Text("Join group?"),
|
||||
message: Text("You will connect to all group members."),
|
||||
primaryButton: .default(
|
||||
Text(incognito ? "Join incognito" : "Join"),
|
||||
action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup) }
|
||||
),
|
||||
secondaryButton: .cancel() { cleanup?() }
|
||||
)
|
||||
case let .groupLinkConnectingConfirmReconnect(connectionLink, connectionPlan, incognito):
|
||||
return Alert(
|
||||
title: Text("Repeat join request?"),
|
||||
message: Text("You are already joining the group via this link!"),
|
||||
primaryButton: .destructive(
|
||||
Text(incognito ? "Join incognito" : "Join"),
|
||||
action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup) }
|
||||
),
|
||||
secondaryButton: .cancel() { cleanup?() }
|
||||
)
|
||||
case let .groupLinkConnecting(_, groupInfo):
|
||||
if let groupInfo = groupInfo {
|
||||
return Alert(
|
||||
title: Text("Group already exists!"),
|
||||
message: Text("You are already joining the group \(groupInfo.displayName)."),
|
||||
dismissButton: .default(Text("OK")) { cleanup?() }
|
||||
)
|
||||
} else {
|
||||
return Alert(
|
||||
title: Text("Already joining the group!"),
|
||||
message: Text("You are already joining the group via this link."),
|
||||
dismissButton: .default(Text("OK")) { cleanup?() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum PlanAndConnectActionSheet: Identifiable {
|
||||
case askCurrentOrIncognitoProfile(connectionLink: String, connectionPlan: ConnectionPlan?, title: LocalizedStringKey)
|
||||
case askCurrentOrIncognitoProfileDestructive(connectionLink: String, connectionPlan: ConnectionPlan, title: LocalizedStringKey)
|
||||
case askCurrentOrIncognitoProfileConnectContactViaAddress(contact: Contact)
|
||||
case ownGroupLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool?, groupInfo: GroupInfo)
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case let .askCurrentOrIncognitoProfile(connectionLink, _, _): return "askCurrentOrIncognitoProfile \(connectionLink)"
|
||||
case let .askCurrentOrIncognitoProfileDestructive(connectionLink, _, _): return "askCurrentOrIncognitoProfileDestructive \(connectionLink)"
|
||||
case let .askCurrentOrIncognitoProfileConnectContactViaAddress(contact): return "askCurrentOrIncognitoProfileConnectContactViaAddress \(contact.contactId)"
|
||||
case let .ownGroupLinkConfirmConnect(connectionLink, _, _, _): return "ownGroupLinkConfirmConnect \(connectionLink)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func planAndConnectActionSheet(_ sheet: PlanAndConnectActionSheet, dismiss: Bool, cleanup: (() -> Void)? = nil) -> ActionSheet {
|
||||
switch sheet {
|
||||
case let .askCurrentOrIncognitoProfile(connectionLink, connectionPlan, title):
|
||||
return ActionSheet(
|
||||
title: Text(title),
|
||||
buttons: [
|
||||
.default(Text("Use current profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: false, cleanup: cleanup) },
|
||||
.default(Text("Use new incognito profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: true, cleanup: cleanup) },
|
||||
.cancel() { cleanup?() }
|
||||
]
|
||||
)
|
||||
case let .askCurrentOrIncognitoProfileDestructive(connectionLink, connectionPlan, title):
|
||||
return ActionSheet(
|
||||
title: Text(title),
|
||||
buttons: [
|
||||
.destructive(Text("Use current profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: false, cleanup: cleanup) },
|
||||
.destructive(Text("Use new incognito profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: true, cleanup: cleanup) },
|
||||
.cancel() { cleanup?() }
|
||||
]
|
||||
)
|
||||
case let .askCurrentOrIncognitoProfileConnectContactViaAddress(contact):
|
||||
return ActionSheet(
|
||||
title: Text("Connect with \(contact.chatViewName)"),
|
||||
buttons: [
|
||||
.default(Text("Use current profile")) { connectContactViaAddress_(contact, dismiss: dismiss, incognito: false, cleanup: cleanup) },
|
||||
.default(Text("Use new incognito profile")) { connectContactViaAddress_(contact, dismiss: dismiss, incognito: true, cleanup: cleanup) },
|
||||
.cancel() { cleanup?() }
|
||||
]
|
||||
)
|
||||
case let .ownGroupLinkConfirmConnect(connectionLink, connectionPlan, incognito, groupInfo):
|
||||
if let incognito = incognito {
|
||||
return ActionSheet(
|
||||
title: Text("Join your group?\nThis is your link for group \(groupInfo.displayName)!"),
|
||||
buttons: [
|
||||
.default(Text("Open group")) { openKnownGroup(groupInfo, dismiss: dismiss, showAlreadyExistsAlert: nil) },
|
||||
.destructive(Text(incognito ? "Join incognito" : "Join with current profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup) },
|
||||
.cancel() { cleanup?() }
|
||||
]
|
||||
)
|
||||
} else {
|
||||
return ActionSheet(
|
||||
title: Text("Join your group?\nThis is your link for group \(groupInfo.displayName)!"),
|
||||
buttons: [
|
||||
.default(Text("Open group")) { openKnownGroup(groupInfo, dismiss: dismiss, showAlreadyExistsAlert: nil) },
|
||||
.destructive(Text("Use current profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: false, cleanup: cleanup) },
|
||||
.destructive(Text("Use new incognito profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: true, cleanup: cleanup) },
|
||||
.cancel() { cleanup?() }
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func planAndConnect(
|
||||
_ connectionLink: String,
|
||||
showAlert: @escaping (PlanAndConnectAlert) -> Void,
|
||||
showActionSheet: @escaping (PlanAndConnectActionSheet) -> Void,
|
||||
dismiss: Bool,
|
||||
incognito: Bool?,
|
||||
cleanup: (() -> Void)? = nil,
|
||||
filterKnownContact: ((Contact) -> Void)? = nil,
|
||||
filterKnownGroup: ((GroupInfo) -> Void)? = nil
|
||||
) {
|
||||
Task {
|
||||
do {
|
||||
let connectionPlan = try await apiConnectPlan(connReq: connectionLink)
|
||||
switch connectionPlan {
|
||||
case let .invitationLink(ilp):
|
||||
switch ilp {
|
||||
case .ok:
|
||||
logger.debug("planAndConnect, .invitationLink, .ok, incognito=\(incognito?.description ?? "nil")")
|
||||
if let incognito = incognito {
|
||||
connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup)
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect via one-time link"))
|
||||
}
|
||||
case .ownLink:
|
||||
logger.debug("planAndConnect, .invitationLink, .ownLink, incognito=\(incognito?.description ?? "nil")")
|
||||
if let incognito = incognito {
|
||||
showAlert(.ownInvitationLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own one-time link!"))
|
||||
}
|
||||
case let .connecting(contact_):
|
||||
logger.debug("planAndConnect, .invitationLink, .connecting, incognito=\(incognito?.description ?? "nil")")
|
||||
if let contact = contact_ {
|
||||
if let f = filterKnownContact {
|
||||
f(contact)
|
||||
} else {
|
||||
openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyConnectingAlert(contact)) }
|
||||
}
|
||||
} else {
|
||||
showAlert(.invitationLinkConnecting(connectionLink: connectionLink))
|
||||
}
|
||||
case let .known(contact):
|
||||
logger.debug("planAndConnect, .invitationLink, .known, incognito=\(incognito?.description ?? "nil")")
|
||||
if let f = filterKnownContact {
|
||||
f(contact)
|
||||
} else {
|
||||
openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyExistsAlert(contact)) }
|
||||
}
|
||||
}
|
||||
case let .contactAddress(cap):
|
||||
switch cap {
|
||||
case .ok:
|
||||
logger.debug("planAndConnect, .contactAddress, .ok, incognito=\(incognito?.description ?? "nil")")
|
||||
if let incognito = incognito {
|
||||
connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup)
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect via contact address"))
|
||||
}
|
||||
case .ownLink:
|
||||
logger.debug("planAndConnect, .contactAddress, .ownLink, incognito=\(incognito?.description ?? "nil")")
|
||||
if let incognito = incognito {
|
||||
showAlert(.ownContactAddressConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own SimpleX address!"))
|
||||
}
|
||||
case .connectingConfirmReconnect:
|
||||
logger.debug("planAndConnect, .contactAddress, .connectingConfirmReconnect, incognito=\(incognito?.description ?? "nil")")
|
||||
if let incognito = incognito {
|
||||
showAlert(.contactAddressConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You have already requested connection!\nRepeat connection request?"))
|
||||
}
|
||||
case let .connectingProhibit(contact):
|
||||
logger.debug("planAndConnect, .contactAddress, .connectingProhibit, incognito=\(incognito?.description ?? "nil")")
|
||||
if let f = filterKnownContact {
|
||||
f(contact)
|
||||
} else {
|
||||
openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyConnectingAlert(contact)) }
|
||||
}
|
||||
case let .known(contact):
|
||||
logger.debug("planAndConnect, .contactAddress, .known, incognito=\(incognito?.description ?? "nil")")
|
||||
if let f = filterKnownContact {
|
||||
f(contact)
|
||||
} else {
|
||||
openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyExistsAlert(contact)) }
|
||||
}
|
||||
case let .contactViaAddress(contact):
|
||||
logger.debug("planAndConnect, .contactAddress, .contactViaAddress, incognito=\(incognito?.description ?? "nil")")
|
||||
if let incognito = incognito {
|
||||
connectContactViaAddress_(contact, dismiss: dismiss, incognito: incognito, cleanup: cleanup)
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfileConnectContactViaAddress(contact: contact))
|
||||
}
|
||||
}
|
||||
case let .groupLink(glp):
|
||||
switch glp {
|
||||
case .ok:
|
||||
if let incognito = incognito {
|
||||
showAlert(.groupLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Join group"))
|
||||
}
|
||||
case let .ownLink(groupInfo):
|
||||
logger.debug("planAndConnect, .groupLink, .ownLink, incognito=\(incognito?.description ?? "nil")")
|
||||
if let f = filterKnownGroup {
|
||||
f(groupInfo)
|
||||
}
|
||||
showActionSheet(.ownGroupLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito, groupInfo: groupInfo))
|
||||
case .connectingConfirmReconnect:
|
||||
logger.debug("planAndConnect, .groupLink, .connectingConfirmReconnect, incognito=\(incognito?.description ?? "nil")")
|
||||
if let incognito = incognito {
|
||||
showAlert(.groupLinkConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito))
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You are already joining the group!\nRepeat join request?"))
|
||||
}
|
||||
case let .connectingProhibit(groupInfo_):
|
||||
logger.debug("planAndConnect, .groupLink, .connectingProhibit, incognito=\(incognito?.description ?? "nil")")
|
||||
showAlert(.groupLinkConnecting(connectionLink: connectionLink, groupInfo: groupInfo_))
|
||||
case let .known(groupInfo):
|
||||
logger.debug("planAndConnect, .groupLink, .known, incognito=\(incognito?.description ?? "nil")")
|
||||
if let f = filterKnownGroup {
|
||||
f(groupInfo)
|
||||
} else {
|
||||
openKnownGroup(groupInfo, dismiss: dismiss) { AlertManager.shared.showAlert(groupAlreadyExistsAlert(groupInfo)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
logger.debug("planAndConnect, plan error")
|
||||
if let incognito = incognito {
|
||||
connectViaLink(connectionLink, connectionPlan: nil, dismiss: dismiss, incognito: incognito, cleanup: cleanup)
|
||||
} else {
|
||||
showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: nil, title: "Connect via link"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func connectContactViaAddress_(_ contact: Contact, dismiss: Bool, incognito: Bool, cleanup: (() -> Void)? = nil) {
|
||||
Task {
|
||||
if dismiss {
|
||||
DispatchQueue.main.async {
|
||||
dismissAllSheets(animated: true)
|
||||
}
|
||||
}
|
||||
_ = await connectContactViaAddress(contact.contactId, incognito)
|
||||
cleanup?()
|
||||
}
|
||||
}
|
||||
|
||||
private func connectViaLink(
|
||||
_ connectionLink: String,
|
||||
connectionPlan: ConnectionPlan?,
|
||||
dismiss: Bool,
|
||||
incognito: Bool,
|
||||
cleanup: (() -> Void)?
|
||||
) {
|
||||
Task {
|
||||
if let (connReqType, pcc) = await apiConnect(incognito: incognito, connReq: connectionLink) {
|
||||
await MainActor.run {
|
||||
ChatModel.shared.updateContactConnection(pcc)
|
||||
}
|
||||
let crt: ConnReqType
|
||||
if let plan = connectionPlan {
|
||||
crt = planToConnReqType(plan)
|
||||
} else {
|
||||
crt = connReqType
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
if dismiss {
|
||||
dismissAllSheets(animated: true) {
|
||||
AlertManager.shared.showAlert(connReqSentAlert(crt))
|
||||
}
|
||||
} else {
|
||||
AlertManager.shared.showAlert(connReqSentAlert(crt))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if dismiss {
|
||||
DispatchQueue.main.async {
|
||||
dismissAllSheets(animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
cleanup?()
|
||||
}
|
||||
}
|
||||
|
||||
func openKnownContact(_ contact: Contact, dismiss: Bool, showAlreadyExistsAlert: (() -> Void)?) {
|
||||
Task {
|
||||
let m = ChatModel.shared
|
||||
if let c = m.getContactChat(contact.contactId) {
|
||||
DispatchQueue.main.async {
|
||||
if dismiss {
|
||||
dismissAllSheets(animated: true) {
|
||||
m.chatId = c.id
|
||||
showAlreadyExistsAlert?()
|
||||
}
|
||||
} else {
|
||||
m.chatId = c.id
|
||||
showAlreadyExistsAlert?()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func openKnownGroup(_ groupInfo: GroupInfo, dismiss: Bool, showAlreadyExistsAlert: (() -> Void)?) {
|
||||
Task {
|
||||
let m = ChatModel.shared
|
||||
if let g = m.getGroupChat(groupInfo.groupId) {
|
||||
DispatchQueue.main.async {
|
||||
if dismiss {
|
||||
dismissAllSheets(animated: true) {
|
||||
m.chatId = g.id
|
||||
showAlreadyExistsAlert?()
|
||||
}
|
||||
} else {
|
||||
m.chatId = g.id
|
||||
showAlreadyExistsAlert?()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func contactAlreadyConnectingAlert(_ contact: Contact) -> Alert {
|
||||
mkAlert(
|
||||
title: "Contact already exists",
|
||||
message: "You are already connecting to \(contact.displayName)."
|
||||
)
|
||||
}
|
||||
|
||||
func groupAlreadyExistsAlert(_ groupInfo: GroupInfo) -> Alert {
|
||||
mkAlert(
|
||||
title: "Group already exists",
|
||||
message: "You are already in group \(groupInfo.displayName)."
|
||||
)
|
||||
}
|
||||
|
||||
enum ConnReqType: Equatable {
|
||||
case invitation
|
||||
case contact
|
||||
case groupLink
|
||||
|
||||
var connReqSentText: LocalizedStringKey {
|
||||
switch self {
|
||||
case .invitation: return "You will be connected when your contact's device is online, please wait or check later!"
|
||||
case .contact: return "You will be connected when your connection request is accepted, please wait or check later!"
|
||||
case .groupLink: return "You will be connected when group link host's device is online, please wait or check later!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func planToConnReqType(_ connectionPlan: ConnectionPlan) -> ConnReqType {
|
||||
switch connectionPlan {
|
||||
case .invitationLink: return .invitation
|
||||
case .contactAddress: return .contact
|
||||
case .groupLink: return .groupLink
|
||||
}
|
||||
}
|
||||
|
||||
func connReqSentAlert(_ type: ConnReqType) -> Alert {
|
||||
return mkAlert(
|
||||
title: "Connection request sent!",
|
||||
message: type.connReqSentText
|
||||
)
|
||||
}
|
||||
|
||||
#Preview {
|
||||
NewChatView(
|
||||
selection: .invite
|
||||
)
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
//
|
||||
// PasteToConnectView.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by Ian Davies on 22/04/2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct PasteToConnectView: View {
|
||||
@Environment(\.dismiss) var dismiss: DismissAction
|
||||
@State private var connectionLink: String = ""
|
||||
@AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false
|
||||
@FocusState private var linkEditorFocused: Bool
|
||||
@State private var alert: PlanAndConnectAlert?
|
||||
@State private var sheet: PlanAndConnectActionSheet?
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Text("Connect via link")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||
.onTapGesture { linkEditorFocused = false }
|
||||
|
||||
Section {
|
||||
linkEditor()
|
||||
|
||||
Button {
|
||||
if connectionLink == "" {
|
||||
connectionLink = UIPasteboard.general.string ?? ""
|
||||
} else {
|
||||
connectionLink = ""
|
||||
}
|
||||
} label: {
|
||||
if connectionLink == "" {
|
||||
settingsRow("doc.plaintext") { Text("Paste") }
|
||||
} else {
|
||||
settingsRow("multiply") { Text("Clear") }
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
connect()
|
||||
} label: {
|
||||
settingsRow("link") { Text("Connect") }
|
||||
}
|
||||
.disabled(connectionLink == "" || connectionLink.trimmingCharacters(in: .whitespaces).firstIndex(of: " ") != nil)
|
||||
|
||||
IncognitoToggle(incognitoEnabled: $incognitoDefault)
|
||||
} footer: {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
sharedProfileInfo(incognitoDefault)
|
||||
Text("You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button.")
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
.alert(item: $alert) { a in planAndConnectAlert(a, dismiss: true) }
|
||||
.actionSheet(item: $sheet) { s in planAndConnectActionSheet(s, dismiss: true) }
|
||||
}
|
||||
|
||||
private func linkEditor() -> some View {
|
||||
ZStack {
|
||||
Group {
|
||||
if connectionLink.isEmpty {
|
||||
TextEditor(text: Binding.constant(NSLocalizedString("Paste the link you received to connect with your contact.", comment: "placeholder")))
|
||||
.foregroundColor(.secondary)
|
||||
.disabled(true)
|
||||
}
|
||||
TextEditor(text: $connectionLink)
|
||||
.onSubmit(connect)
|
||||
.textInputAutocapitalization(.never)
|
||||
.disableAutocorrection(true)
|
||||
.focused($linkEditorFocused)
|
||||
}
|
||||
.allowsTightening(false)
|
||||
.padding(.horizontal, -5)
|
||||
.padding(.top, -8)
|
||||
.frame(height: 180, alignment: .topLeading)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
private func connect() {
|
||||
let link = connectionLink.trimmingCharacters(in: .whitespaces)
|
||||
planAndConnect(
|
||||
link,
|
||||
showAlert: { alert = $0 },
|
||||
showActionSheet: { sheet = $0 },
|
||||
dismiss: true,
|
||||
incognito: incognitoDefault
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct PasteToConnectView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
PasteToConnectView()
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,10 @@ struct SimpleXLinkQRCode: View {
|
||||
let uri: String
|
||||
var withLogo: Bool = true
|
||||
var tintColor = UIColor(red: 0.023, green: 0.176, blue: 0.337, alpha: 1)
|
||||
var onShare: (() -> Void)? = nil
|
||||
|
||||
var body: some View {
|
||||
QRCode(uri: simplexChatLink(uri), withLogo: withLogo, tintColor: tintColor)
|
||||
QRCode(uri: simplexChatLink(uri), withLogo: withLogo, tintColor: tintColor, onShare: onShare)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +41,7 @@ struct QRCode: View {
|
||||
let uri: String
|
||||
var withLogo: Bool = true
|
||||
var tintColor = UIColor(red: 0.023, green: 0.176, blue: 0.337, alpha: 1)
|
||||
var onShare: (() -> Void)? = nil
|
||||
@State private var image: UIImage? = nil
|
||||
@State private var makeScreenshotFunc: () -> Void = {}
|
||||
|
||||
@@ -65,6 +67,7 @@ struct QRCode: View {
|
||||
makeScreenshotFunc = {
|
||||
let size = CGSizeMake(1024 / UIScreen.main.scale, 1024 / UIScreen.main.scale)
|
||||
showShareSheet(items: [makeScreenshot(geo.frame(in: .local).origin, size)])
|
||||
onShare?()
|
||||
}
|
||||
}
|
||||
.frame(width: geo.size.width, height: geo.size.height)
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
//
|
||||
// ConnectContactView.swift
|
||||
// SimpleX
|
||||
//
|
||||
// Created by Evgeny Poberezkin on 29/01/2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
import CodeScanner
|
||||
|
||||
struct ScanToConnectView: View {
|
||||
@Environment(\.dismiss) var dismiss: DismissAction
|
||||
@AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false
|
||||
@State private var alert: PlanAndConnectAlert?
|
||||
@State private var sheet: PlanAndConnectActionSheet?
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Scan QR code")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(.vertical)
|
||||
|
||||
CodeScannerView(codeTypes: [.qr], scanMode: .continuous, completion: processQRCode)
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.cornerRadius(12)
|
||||
|
||||
IncognitoToggle(incognitoEnabled: $incognitoDefault)
|
||||
.padding(.horizontal)
|
||||
.padding(.vertical, 6)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
||||
.fill(Color(uiColor: .systemBackground))
|
||||
)
|
||||
.padding(.top)
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
sharedProfileInfo(incognitoDefault)
|
||||
Text("If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link.")
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.padding()
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
||||
}
|
||||
.background(Color(.systemGroupedBackground))
|
||||
.alert(item: $alert) { a in planAndConnectAlert(a, dismiss: true) }
|
||||
.actionSheet(item: $sheet) { s in planAndConnectActionSheet(s, dismiss: true) }
|
||||
}
|
||||
|
||||
func processQRCode(_ resp: Result<ScanResult, ScanError>) {
|
||||
switch resp {
|
||||
case let .success(r):
|
||||
planAndConnect(
|
||||
r.string,
|
||||
showAlert: { alert = $0 },
|
||||
showActionSheet: { sheet = $0 },
|
||||
dismiss: true,
|
||||
incognito: incognitoDefault
|
||||
)
|
||||
case let .failure(e):
|
||||
logger.error("ConnectContactView.processQRCode QR code error: \(e.localizedDescription)")
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ConnectContactView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ScanToConnectView()
|
||||
}
|
||||
}
|
||||
@@ -11,12 +11,14 @@ import SimpleXChat
|
||||
|
||||
enum UserProfileAlert: Identifiable {
|
||||
case duplicateUserError
|
||||
case invalidDisplayNameError
|
||||
case createUserError(error: LocalizedStringKey)
|
||||
case invalidNameError(validName: String)
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case .duplicateUserError: return "duplicateUserError"
|
||||
case .invalidDisplayNameError: return "invalidDisplayNameError"
|
||||
case .createUserError: return "createUserError"
|
||||
case let .invalidNameError(validName): return "invalidNameError \(validName)"
|
||||
}
|
||||
@@ -187,6 +189,12 @@ private func createProfile(_ displayName: String, showAlert: (UserProfileAlert)
|
||||
} else {
|
||||
showAlert(.duplicateUserError)
|
||||
}
|
||||
case .chatCmdError(_, .error(.invalidDisplayName)):
|
||||
if m.currentUser == nil {
|
||||
AlertManager.shared.showAlert(invalidDisplayNameAlert)
|
||||
} else {
|
||||
showAlert(.invalidDisplayNameError)
|
||||
}
|
||||
default:
|
||||
let err: LocalizedStringKey = "Error: \(responseError(error))"
|
||||
if m.currentUser == nil {
|
||||
@@ -207,6 +215,7 @@ private func canCreateProfile(_ displayName: String) -> Bool {
|
||||
func userProfileAlert(_ alert: UserProfileAlert, _ displayName: Binding<String>) -> Alert {
|
||||
switch alert {
|
||||
case .duplicateUserError: return duplicateUserAlert
|
||||
case .invalidDisplayNameError: return invalidDisplayNameAlert
|
||||
case let .createUserError(err): return creatUserErrorAlert(err)
|
||||
case let .invalidNameError(name): return createInvalidNameAlert(name, displayName)
|
||||
}
|
||||
@@ -219,6 +228,13 @@ private var duplicateUserAlert: Alert {
|
||||
)
|
||||
}
|
||||
|
||||
private var invalidDisplayNameAlert: Alert {
|
||||
Alert(
|
||||
title: Text("Invalid display name!"),
|
||||
message: Text("This display name is invalid. Please choose another name.")
|
||||
)
|
||||
}
|
||||
|
||||
private func creatUserErrorAlert(_ err: LocalizedStringKey) -> Alert {
|
||||
Alert(
|
||||
title: Text("Error creating profile!"),
|
||||
|
||||
@@ -314,6 +314,37 @@ private let versionDescriptions: [VersionDescription] = [
|
||||
),
|
||||
]
|
||||
),
|
||||
VersionDescription(
|
||||
version: "v5.5",
|
||||
post: URL(string: "https://simplex.chat/blog/20240124-simplex-chat-infrastructure-costs-v5-5-simplex-ux-private-notes-group-history.html"),
|
||||
features: [
|
||||
FeatureDescription(
|
||||
icon: "folder",
|
||||
title: "Private notes",
|
||||
description: "With encrypted files and media."
|
||||
),
|
||||
FeatureDescription(
|
||||
icon: "link",
|
||||
title: "Paste link to connect!",
|
||||
description: "Search bar accepts invitation links."
|
||||
),
|
||||
FeatureDescription(
|
||||
icon: "bubble.left.and.bubble.right",
|
||||
title: "Join group conversations",
|
||||
description: "Recent history and improved [directory bot](simplex:/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)."
|
||||
),
|
||||
FeatureDescription(
|
||||
icon: "battery.50",
|
||||
title: "Improved message delivery",
|
||||
description: "With reduced battery usage."
|
||||
),
|
||||
FeatureDescription(
|
||||
icon: "character",
|
||||
title: "Turkish interface",
|
||||
description: "Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"
|
||||
),
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
private let lastVersion = versionDescriptions.last!.version
|
||||
|
||||
@@ -42,25 +42,6 @@ struct DeveloperView: View {
|
||||
} footer: {
|
||||
(developerTools ? Text("Show:") : Text("Hide:")) + Text(" ") + Text("Database IDs and Transport isolation option.")
|
||||
}
|
||||
|
||||
// Section {
|
||||
// settingsRow("arrow.up.doc") {
|
||||
// Toggle("Send videos and files via XFTP", isOn: $xftpSendEnabled)
|
||||
// .onChange(of: xftpSendEnabled) { _ in
|
||||
// do {
|
||||
// try setXFTPConfig(getXFTPCfg())
|
||||
// } catch {
|
||||
// logger.error("setXFTPConfig: cannot set XFTP config \(responseError(error))")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// } header: {
|
||||
// Text("Experimental")
|
||||
// } footer: {
|
||||
// if xftpSendEnabled {
|
||||
// Text("v4.6.1+ is required to receive via XFTP.")
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,24 +10,23 @@ import SwiftUI
|
||||
|
||||
struct IncognitoHelp: View {
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
List {
|
||||
Text("Incognito mode")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(.vertical)
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
Group {
|
||||
Text("Incognito mode protects your privacy by using a new random profile for each contact.")
|
||||
Text("It allows having many anonymous connections without any shared data between them in a single chat profile.")
|
||||
Text("When you share an incognito profile with somebody, this profile will be used for the groups they invite you to.")
|
||||
}
|
||||
.padding(.bottom)
|
||||
}
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||
VStack(alignment: .leading, spacing: 18) {
|
||||
Text("Incognito mode protects your privacy by using a new random profile for each contact.")
|
||||
Text("It allows having many anonymous connections without any shared data between them in a single chat profile.")
|
||||
Text("When you share an incognito profile with somebody, this profile will be used for the groups they invite you to.")
|
||||
Text("Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode).")
|
||||
}
|
||||
.listRowBackground(Color.clear)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,6 @@ struct PreferencesView: View {
|
||||
|
||||
private func featureFooter(_ feature: ChatFeature, _ allowFeature: Binding<FeatureAllowed>) -> some View {
|
||||
Text(feature.allowDescription(allowFeature.wrappedValue))
|
||||
.frame(height: 36, alignment: .topLeading)
|
||||
}
|
||||
|
||||
private func savePreferences() {
|
||||
|
||||
@@ -491,14 +491,23 @@ struct SimplexLockView: View {
|
||||
showLAAlert(.laPasscodeNotChangedAlert)
|
||||
}
|
||||
case .enableSelfDestruct:
|
||||
SetAppPasscodeView(passcodeKeychain: kcSelfDestructPassword, title: "Set passcode", reason: NSLocalizedString("Enable self-destruct passcode", comment: "set passcode view")) {
|
||||
SetAppPasscodeView(
|
||||
passcodeKeychain: kcSelfDestructPassword,
|
||||
prohibitedPasscodeKeychain: kcAppPassword,
|
||||
title: "Set passcode",
|
||||
reason: NSLocalizedString("Enable self-destruct passcode", comment: "set passcode view")
|
||||
) {
|
||||
updateSelfDestruct()
|
||||
showLAAlert(.laSelfDestructPasscodeSetAlert)
|
||||
} cancel: {
|
||||
revertSelfDestruct()
|
||||
}
|
||||
case .changeSelfDestructPasscode:
|
||||
SetAppPasscodeView(passcodeKeychain: kcSelfDestructPassword, reason: NSLocalizedString("Change self-destruct passcode", comment: "set passcode view")) {
|
||||
SetAppPasscodeView(
|
||||
passcodeKeychain: kcSelfDestructPassword,
|
||||
prohibitedPasscodeKeychain: kcAppPassword,
|
||||
reason: NSLocalizedString("Change self-destruct passcode", comment: "set passcode view")
|
||||
) {
|
||||
showLAAlert(.laSelfDestructPasscodeChangedAlert)
|
||||
} cancel: {
|
||||
showLAAlert(.laPasscodeNotChangedAlert)
|
||||
|
||||
@@ -95,6 +95,12 @@ let appDefaults: [String: Any] = [
|
||||
DEFAULT_CONNECT_REMOTE_VIA_MULTICAST_AUTO: true,
|
||||
]
|
||||
|
||||
// not used anymore
|
||||
enum ConnectViaLinkTab: String {
|
||||
case scan
|
||||
case paste
|
||||
}
|
||||
|
||||
enum SimpleXLinkMode: String, Identifiable {
|
||||
case description
|
||||
case full
|
||||
@@ -153,37 +159,42 @@ struct SettingsView: View {
|
||||
}
|
||||
|
||||
@ViewBuilder func settingsView() -> some View {
|
||||
let user: User = chatModel.currentUser!
|
||||
let user = chatModel.currentUser
|
||||
NavigationView {
|
||||
List {
|
||||
Section("You") {
|
||||
NavigationLink {
|
||||
UserProfile()
|
||||
.navigationTitle("Your current profile")
|
||||
} label: {
|
||||
ProfilePreview(profileOf: user)
|
||||
.padding(.leading, -8)
|
||||
if let user = user {
|
||||
NavigationLink {
|
||||
UserProfile()
|
||||
.navigationTitle("Your current profile")
|
||||
} label: {
|
||||
ProfilePreview(profileOf: user)
|
||||
.padding(.leading, -8)
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink {
|
||||
UserProfilesView()
|
||||
UserProfilesView(showSettings: $showSettings)
|
||||
} label: {
|
||||
settingsRow("person.crop.rectangle.stack") { Text("Your chat profiles") }
|
||||
}
|
||||
|
||||
NavigationLink {
|
||||
UserAddressView(shareViaProfile: chatModel.currentUser!.addressShared)
|
||||
.navigationTitle("SimpleX address")
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
} label: {
|
||||
settingsRow("qrcode") { Text("Your SimpleX address") }
|
||||
}
|
||||
|
||||
NavigationLink {
|
||||
PreferencesView(profile: user.profile, preferences: user.fullPreferences, currentPreferences: user.fullPreferences)
|
||||
.navigationTitle("Your preferences")
|
||||
} label: {
|
||||
settingsRow("switch.2") { Text("Chat preferences") }
|
||||
if let user = user {
|
||||
NavigationLink {
|
||||
UserAddressView(shareViaProfile: user.addressShared)
|
||||
.navigationTitle("SimpleX address")
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
} label: {
|
||||
settingsRow("qrcode") { Text("Your SimpleX address") }
|
||||
}
|
||||
|
||||
NavigationLink {
|
||||
PreferencesView(profile: user.profile, preferences: user.fullPreferences, currentPreferences: user.fullPreferences)
|
||||
.navigationTitle("Your preferences")
|
||||
} label: {
|
||||
settingsRow("switch.2") { Text("Chat preferences") }
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink {
|
||||
@@ -244,12 +255,14 @@ struct SettingsView: View {
|
||||
}
|
||||
|
||||
Section("Help") {
|
||||
NavigationLink {
|
||||
ChatHelp(showSettings: $showSettings)
|
||||
.navigationTitle("Welcome \(user.displayName)!")
|
||||
.frame(maxHeight: .infinity, alignment: .top)
|
||||
} label: {
|
||||
settingsRow("questionmark") { Text("How to use it") }
|
||||
if let user = user {
|
||||
NavigationLink {
|
||||
ChatHelp(showSettings: $showSettings)
|
||||
.navigationTitle("Welcome \(user.displayName)!")
|
||||
.frame(maxHeight: .infinity, alignment: .top)
|
||||
} label: {
|
||||
settingsRow("questionmark") { Text("How to use it") }
|
||||
}
|
||||
}
|
||||
NavigationLink {
|
||||
WhatsNewView(viaSettings: true)
|
||||
|
||||
@@ -8,6 +8,7 @@ import SimpleXChat
|
||||
|
||||
struct UserProfilesView: View {
|
||||
@EnvironmentObject private var m: ChatModel
|
||||
@Binding var showSettings: Bool
|
||||
@Environment(\.editMode) private var editMode
|
||||
@AppStorage(DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE) private var showHiddenProfilesNotice = true
|
||||
@AppStorage(DEFAULT_SHOW_MUTE_PROFILE_ALERT) private var showMuteProfileAlert = true
|
||||
@@ -25,7 +26,6 @@ struct UserProfilesView: View {
|
||||
|
||||
private enum UserProfilesAlert: Identifiable {
|
||||
case deleteUser(user: User, delSMPQueues: Bool)
|
||||
case cantDeleteLastUser
|
||||
case hiddenProfilesNotice
|
||||
case muteProfileAlert
|
||||
case activateUserError(error: String)
|
||||
@@ -34,7 +34,6 @@ struct UserProfilesView: View {
|
||||
var id: String {
|
||||
switch self {
|
||||
case let .deleteUser(user, delSMPQueues): return "deleteUser \(user.userId) \(delSMPQueues)"
|
||||
case .cantDeleteLastUser: return "cantDeleteLastUser"
|
||||
case .hiddenProfilesNotice: return "hiddenProfilesNotice"
|
||||
case .muteProfileAlert: return "muteProfileAlert"
|
||||
case let .activateUserError(err): return "activateUserError \(err)"
|
||||
@@ -78,7 +77,7 @@ struct UserProfilesView: View {
|
||||
Section {
|
||||
let users = filteredUsers()
|
||||
let v = ForEach(users) { u in
|
||||
userView(u.user, allowDelete: users.count > 1)
|
||||
userView(u.user)
|
||||
}
|
||||
if #available(iOS 16, *) {
|
||||
v.onDelete { indexSet in
|
||||
@@ -146,13 +145,6 @@ struct UserProfilesView: View {
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
case .cantDeleteLastUser:
|
||||
return Alert(
|
||||
title: Text("Can't delete user profile!"),
|
||||
message: m.users.count > 1
|
||||
? Text("There should be at least one visible user profile.")
|
||||
: Text("There should be at least one user profile.")
|
||||
)
|
||||
case .hiddenProfilesNotice:
|
||||
return Alert(
|
||||
title: Text("Make profile private!"),
|
||||
@@ -280,11 +272,21 @@ struct UserProfilesView: View {
|
||||
if let newActive = m.users.first(where: { u in !u.user.activeUser && !u.user.hidden }) {
|
||||
try await changeActiveUserAsync_(newActive.user.userId, viewPwd: nil)
|
||||
try await deleteUser()
|
||||
} else {
|
||||
// Deleting the last visible user while having hidden one(s)
|
||||
try await deleteUser()
|
||||
try await changeActiveUserAsync_(nil, viewPwd: nil)
|
||||
await MainActor.run {
|
||||
onboardingStageDefault.set(.step1_SimpleXInfo)
|
||||
m.onboardingStage = .step1_SimpleXInfo
|
||||
showSettings = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try await deleteUser()
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("Error deleting user profile: \(error)")
|
||||
let a = getErrorAlert(error, "Error deleting user profile")
|
||||
alert = .error(title: a.title, error: a.message)
|
||||
}
|
||||
@@ -295,7 +297,7 @@ struct UserProfilesView: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func userView(_ user: User, allowDelete: Bool) -> some View {
|
||||
@ViewBuilder private func userView(_ user: User) -> some View {
|
||||
let v = Button {
|
||||
Task {
|
||||
do {
|
||||
@@ -323,9 +325,7 @@ struct UserProfilesView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.disabled(user.activeUser)
|
||||
.foregroundColor(.primary)
|
||||
.deleteDisabled(!allowDelete)
|
||||
.swipeActions(edge: .leading, allowsFullSwipe: true) {
|
||||
if user.hidden {
|
||||
Button("Unhide") {
|
||||
@@ -361,8 +361,6 @@ struct UserProfilesView: View {
|
||||
}
|
||||
if #available(iOS 16, *) {
|
||||
v
|
||||
} else if !allowDelete {
|
||||
v
|
||||
} else {
|
||||
v.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
Button("Delete", role: .destructive) {
|
||||
@@ -373,12 +371,8 @@ struct UserProfilesView: View {
|
||||
}
|
||||
|
||||
private func confirmDeleteUser(_ user: User) {
|
||||
if m.users.count > 1 && (user.hidden || visibleUsersCount > 1) {
|
||||
showDeleteConfirmation = true
|
||||
userToDelete = user
|
||||
} else {
|
||||
alert = .cantDeleteLastUser
|
||||
}
|
||||
showDeleteConfirmation = true
|
||||
userToDelete = user
|
||||
}
|
||||
|
||||
private func setUserPrivacy(_ user: User, successAlert: UserProfilesAlert? = nil, _ api: @escaping () async throws -> User) {
|
||||
@@ -409,6 +403,6 @@ public func chatPasswordHash(_ pwd: String, _ salt: String) -> String {
|
||||
|
||||
struct UserProfilesView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
UserProfilesView()
|
||||
UserProfilesView(showSettings: Binding.constant(true))
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -89,6 +89,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ and %@" xml:space="preserve">
|
||||
<source>%@ and %@</source>
|
||||
<target>%@ a %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ and %@ connected" xml:space="preserve">
|
||||
@@ -103,6 +104,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ connected" xml:space="preserve">
|
||||
<source>%@ connected</source>
|
||||
<target>%@ připojen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ is connected!" xml:space="preserve">
|
||||
@@ -212,6 +214,10 @@
|
||||
<source>%lld messages blocked</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld messages blocked by admin" xml:space="preserve">
|
||||
<source>%lld messages blocked by admin</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld messages marked deleted" xml:space="preserve">
|
||||
<source>%lld messages marked deleted</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -303,14 +309,17 @@
|
||||
<target>)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Add contact**: to create a new invitation link, or connect via a link you received." xml:space="preserve">
|
||||
<source>**Add contact**: to create a new invitation link, or connect via a link you received.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Add new contact**: to create your one-time QR Code for your contact." xml:space="preserve">
|
||||
<source>**Add new contact**: to create your one-time QR Code or link for your contact.</source>
|
||||
<target>**Přidat nový kontakt**: pro vytvoření jednorázového QR kódu nebo odkazu pro váš kontakt.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Create link / QR code** for your contact to use." xml:space="preserve">
|
||||
<source>**Create link / QR code** for your contact to use.</source>
|
||||
<target>**Vytvořte odkaz / QR kód** pro váš kontakt.</target>
|
||||
<trans-unit id="**Create group**: to create a new group." xml:space="preserve">
|
||||
<source>**Create group**: to create a new group.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." xml:space="preserve">
|
||||
@@ -323,11 +332,6 @@
|
||||
<target>**Nejsoukromější**: nepoužívejte server oznámení SimpleX Chat, pravidelně kontrolujte zprávy na pozadí (závisí na tom, jak často aplikaci používáte).</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Paste received link** or open it in the browser and tap **Open in mobile app**." xml:space="preserve">
|
||||
<source>**Paste received link** or open it in the browser and tap **Open in mobile app**.</source>
|
||||
<target>**Vložte přijatý odkaz** nebo jej otevřete v prohlížeči a klepněte na **Otevřít v mobilní aplikaci**.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Please note**: you will NOT be able to recover or change passphrase if you lose it." xml:space="preserve">
|
||||
<source>**Please note**: you will NOT be able to recover or change passphrase if you lose it.</source>
|
||||
<target>**Upozornění**: Pokud heslo ztratíte, NEBUDETE jej moci obnovit ani změnit.</target>
|
||||
@@ -338,11 +342,6 @@
|
||||
<target>**Doporučeno**: Token zařízení a oznámení se odesílají na oznamovací server SimpleX Chat, ale nikoli obsah, velikost nebo od koho jsou zprávy.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Scan QR code**: to connect to your contact in person or via video call." xml:space="preserve">
|
||||
<source>**Scan QR code**: to connect to your contact in person or via video call.</source>
|
||||
<target>** Naskenujte QR kód**: pro připojení ke kontaktu osobně nebo prostřednictvím videohovoru.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Warning**: Instant push notifications require passphrase saved in Keychain." xml:space="preserve">
|
||||
<source>**Warning**: Instant push notifications require passphrase saved in Keychain.</source>
|
||||
<target>**Upozornění**: Okamžitě doručovaná oznámení vyžadují přístupové heslo uložené v Klíčence.</target>
|
||||
@@ -372,7 +371,7 @@
|
||||
<source>- connect to [directory service](simplex:/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) (BETA)!
|
||||
- delivery receipts (up to 20 members).
|
||||
- faster and more stable.</source>
|
||||
<target>- připojit k [adresářová služba](simplex:/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.cibule) (BETA)!
|
||||
<target>- připojit k [adresářová služba](simplex:/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) (BETA)!
|
||||
- doručenky (až 20 členů).
|
||||
- Rychlejší a stabilnější.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -440,11 +439,6 @@
|
||||
<target>1 týden</target>
|
||||
<note>time interval</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="1-time link" xml:space="preserve">
|
||||
<source>1-time link</source>
|
||||
<target>Jednorázový odkaz</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="5 minutes" xml:space="preserve">
|
||||
<source>5 minutes</source>
|
||||
<target>5 minut</target>
|
||||
@@ -560,6 +554,10 @@
|
||||
<target>Přidejte adresu do svého profilu, aby ji vaše kontakty mohly sdílet s dalšími lidmi. Aktualizace profilu bude zaslána vašim kontaktům.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add contact" xml:space="preserve">
|
||||
<source>Add contact</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add preset servers" xml:space="preserve">
|
||||
<source>Add preset servers</source>
|
||||
<target>Přidejte přednastavené servery</target>
|
||||
@@ -630,6 +628,10 @@
|
||||
<target>Všichni členové skupiny zůstanou připojeni.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages will be deleted - this cannot be undone!" xml:space="preserve">
|
||||
<source>All messages will be deleted - this cannot be undone!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." xml:space="preserve">
|
||||
<source>All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you.</source>
|
||||
<target>Všechny zprávy budou smazány – tuto akci nelze vrátit zpět! Zprávy budou smazány POUZE pro vás.</target>
|
||||
@@ -664,9 +666,9 @@
|
||||
<target>Povolte mizící zprávy, pouze pokud vám to váš kontakt dovolí.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow irreversible message deletion only if your contact allows it to you." xml:space="preserve">
|
||||
<source>Allow irreversible message deletion only if your contact allows it to you.</source>
|
||||
<target>Povolte nevratné smazání zprávy pouze v případě, že vám to váš kontakt dovolí.</target>
|
||||
<trans-unit id="Allow irreversible message deletion only if your contact allows it to you. (24 hours)" xml:space="preserve">
|
||||
<source>Allow irreversible message deletion only if your contact allows it to you. (24 hours)</source>
|
||||
<target>Povolte nevratné smazání zprávy pouze v případě, že vám to váš kontakt dovolí. (24 hodin)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow message reactions only if your contact allows them." xml:space="preserve">
|
||||
@@ -689,9 +691,9 @@
|
||||
<target>Povolit odesílání mizících zpráv.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow to irreversibly delete sent messages." xml:space="preserve">
|
||||
<source>Allow to irreversibly delete sent messages.</source>
|
||||
<target>Povolit nevratné smazání odeslaných zpráv.</target>
|
||||
<trans-unit id="Allow to irreversibly delete sent messages. (24 hours)" xml:space="preserve">
|
||||
<source>Allow to irreversibly delete sent messages. (24 hours)</source>
|
||||
<target>Povolit nevratné smazání odeslaných zpráv. (24 hodin)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow to send files and media." xml:space="preserve">
|
||||
@@ -724,9 +726,9 @@
|
||||
<target>Povolte svým kontaktům vám volat.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow your contacts to irreversibly delete sent messages." xml:space="preserve">
|
||||
<source>Allow your contacts to irreversibly delete sent messages.</source>
|
||||
<target>Umožněte svým kontaktům nevratně odstranit odeslané zprávy.</target>
|
||||
<trans-unit id="Allow your contacts to irreversibly delete sent messages. (24 hours)" xml:space="preserve">
|
||||
<source>Allow your contacts to irreversibly delete sent messages. (24 hours)</source>
|
||||
<target>Umožněte svým kontaktům nevratně odstranit odeslané zprávy. (24 hodin)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow your contacts to send disappearing messages." xml:space="preserve">
|
||||
@@ -899,6 +901,10 @@
|
||||
<source>Block</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Block for all" xml:space="preserve">
|
||||
<source>Block for all</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Block group members" xml:space="preserve">
|
||||
<source>Block group members</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -907,18 +913,26 @@
|
||||
<source>Block member</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Block member for all?" xml:space="preserve">
|
||||
<source>Block member for all?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Block member?" xml:space="preserve">
|
||||
<source>Block member?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Blocked by admin" xml:space="preserve">
|
||||
<source>Blocked by admin</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
|
||||
<source>Both you and your contact can add message reactions.</source>
|
||||
<target>Vy i váš kontakt můžete přidávat reakce na zprávy.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Both you and your contact can irreversibly delete sent messages." xml:space="preserve">
|
||||
<source>Both you and your contact can irreversibly delete sent messages.</source>
|
||||
<target>Vy i váš kontakt můžete nevratně mazat odeslané zprávy.</target>
|
||||
<trans-unit id="Both you and your contact can irreversibly delete sent messages. (24 hours)" xml:space="preserve">
|
||||
<source>Both you and your contact can irreversibly delete sent messages. (24 hours)</source>
|
||||
<target>Vy i váš kontakt můžete nevratně mazat odeslané zprávy. (24 hodin)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Both you and your contact can make calls." xml:space="preserve">
|
||||
@@ -956,9 +970,8 @@
|
||||
<target>Hovory</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Can't delete user profile!" xml:space="preserve">
|
||||
<source>Can't delete user profile!</source>
|
||||
<target>Nemohu smazat uživatelský profil!</target>
|
||||
<trans-unit id="Camera not available" xml:space="preserve">
|
||||
<source>Camera not available</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Can't invite contact!" xml:space="preserve">
|
||||
@@ -1072,6 +1085,10 @@
|
||||
<target>Chat je zastaven</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." xml:space="preserve">
|
||||
<source>Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat preferences" xml:space="preserve">
|
||||
<source>Chat preferences</source>
|
||||
<target>Předvolby chatu</target>
|
||||
@@ -1117,6 +1134,10 @@
|
||||
<target>Vyčistit konverzaci?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Clear private notes?" xml:space="preserve">
|
||||
<source>Clear private notes?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Clear verification" xml:space="preserve">
|
||||
<source>Clear verification</source>
|
||||
<target>Zrušte ověření</target>
|
||||
@@ -1208,11 +1229,6 @@ This is your own one-time link!</source>
|
||||
<target>Připojte se prostřednictvím odkazu</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via link / QR code" xml:space="preserve">
|
||||
<source>Connect via link / QR code</source>
|
||||
<target>Připojit se prostřednictvím odkazu / QR kódu</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via one-time link" xml:space="preserve">
|
||||
<source>Connect via one-time link</source>
|
||||
<target>Připojit se jednorázovým odkazem</target>
|
||||
@@ -1380,11 +1396,6 @@ This is your own one-time link!</source>
|
||||
<target>Vytvořit nový profil v [desktop app](https://simplex.chat/downloads/). 💻</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create one-time invitation link" xml:space="preserve">
|
||||
<source>Create one-time invitation link</source>
|
||||
<target>Vytvořit jednorázovou pozvánku</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create profile" xml:space="preserve">
|
||||
<source>Create profile</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -1404,11 +1415,23 @@ This is your own one-time link!</source>
|
||||
<target>Vytvořte si profil</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Created at" xml:space="preserve">
|
||||
<source>Created at</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Created at: %@" xml:space="preserve">
|
||||
<source>Created at: %@</source>
|
||||
<note>copied message info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Created on %@" xml:space="preserve">
|
||||
<source>Created on %@</source>
|
||||
<target>Vytvořeno na %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Creating link…" xml:space="preserve">
|
||||
<source>Creating link…</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Current Passcode" xml:space="preserve">
|
||||
<source>Current Passcode</source>
|
||||
<target>Aktuální heslo</target>
|
||||
@@ -1880,6 +1903,10 @@ This cannot be undone!</source>
|
||||
<target>Udělat později</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Do not send history to new members." xml:space="preserve">
|
||||
<source>Do not send history to new members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Don't create address" xml:space="preserve">
|
||||
<source>Don't create address</source>
|
||||
<target>Nevytvářet adresu</target>
|
||||
@@ -1950,6 +1977,10 @@ This cannot be undone!</source>
|
||||
<target>Povolit automatické mazání zpráv?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enable camera access" xml:space="preserve">
|
||||
<source>Enable camera access</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enable for all" xml:space="preserve">
|
||||
<source>Enable for all</source>
|
||||
<target>Povolit pro všechny</target>
|
||||
@@ -2015,6 +2046,10 @@ This cannot be undone!</source>
|
||||
<target>Šifrovaná zpráva nebo jiná událost</target>
|
||||
<note>notification</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Encrypted message: app is stopped" xml:space="preserve">
|
||||
<source>Encrypted message: app is stopped</source>
|
||||
<note>notification</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Encrypted message: database error" xml:space="preserve">
|
||||
<source>Encrypted message: database error</source>
|
||||
<target>Šifrovaná zpráva: chyba databáze</target>
|
||||
@@ -2155,6 +2190,10 @@ This cannot be undone!</source>
|
||||
<target>Chyba vytvoření kontaktu člena</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error creating message" xml:space="preserve">
|
||||
<source>Error creating message</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error creating profile!" xml:space="preserve">
|
||||
<source>Error creating profile!</source>
|
||||
<target>Chyba při vytváření profilu!</target>
|
||||
@@ -2240,6 +2279,10 @@ This cannot be undone!</source>
|
||||
<target>Chyba načítání %@ serverů</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error opening chat" xml:space="preserve">
|
||||
<source>Error opening chat</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error receiving file" xml:space="preserve">
|
||||
<source>Error receiving file</source>
|
||||
<target>Chyba při příjmu souboru</target>
|
||||
@@ -2280,6 +2323,10 @@ This cannot be undone!</source>
|
||||
<target>Chyba ukládání hesla uživatele</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error scanning code: %@" xml:space="preserve">
|
||||
<source>Error scanning code: %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error sending email" xml:space="preserve">
|
||||
<source>Error sending email</source>
|
||||
<target>Chyba odesílání e-mailu</target>
|
||||
@@ -2604,9 +2651,9 @@ This cannot be undone!</source>
|
||||
<target>Členové skupin mohou přidávat reakce na zprávy.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group members can irreversibly delete sent messages." xml:space="preserve">
|
||||
<source>Group members can irreversibly delete sent messages.</source>
|
||||
<target>Členové skupiny mohou nevratně mazat odeslané zprávy.</target>
|
||||
<trans-unit id="Group members can irreversibly delete sent messages. (24 hours)" xml:space="preserve">
|
||||
<source>Group members can irreversibly delete sent messages. (24 hours)</source>
|
||||
<target>Členové skupiny mohou nevratně mazat odeslané zprávy. (24 hodin)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group members can send direct messages." xml:space="preserve">
|
||||
@@ -2714,6 +2761,10 @@ This cannot be undone!</source>
|
||||
<target>Historie</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="History is not sent to new members." xml:space="preserve">
|
||||
<source>History is not sent to new members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="How SimpleX works" xml:space="preserve">
|
||||
<source>How SimpleX works</source>
|
||||
<target>Jak SimpleX funguje</target>
|
||||
@@ -2749,11 +2800,6 @@ This cannot be undone!</source>
|
||||
<target>Pokud se nemůžete setkat osobně, zobrazte QR kód ve videohovoru nebo sdílejte odkaz.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link." xml:space="preserve">
|
||||
<source>If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link.</source>
|
||||
<target>Pokud se nemůžete setkat osobně, můžete **naskenovat QR kód během videohovoru**, nebo váš kontakt může sdílet odkaz na pozvánku.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="If you enter this passcode when opening the app, all app data will be irreversibly removed!" xml:space="preserve">
|
||||
<source>If you enter this passcode when opening the app, all app data will be irreversibly removed!</source>
|
||||
<target>Pokud tento přístupový kód zadáte při otevření aplikace, všechna data budou nenávratně smazána!</target>
|
||||
@@ -2809,6 +2855,10 @@ This cannot be undone!</source>
|
||||
<target>Import databáze</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Improved message delivery" xml:space="preserve">
|
||||
<source>Improved message delivery</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Improved privacy and security" xml:space="preserve">
|
||||
<source>Improved privacy and security</source>
|
||||
<target>Vylepšená ochrana soukromí a zabezpečení</target>
|
||||
@@ -2909,15 +2959,31 @@ This cannot be undone!</source>
|
||||
<target>Rozhranní</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid QR code" xml:space="preserve">
|
||||
<source>Invalid QR code</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid connection link" xml:space="preserve">
|
||||
<source>Invalid connection link</source>
|
||||
<target>Neplatný odkaz na spojení</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid display name!" xml:space="preserve">
|
||||
<source>Invalid display name!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid link" xml:space="preserve">
|
||||
<source>Invalid link</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid name!" xml:space="preserve">
|
||||
<source>Invalid name!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid response" xml:space="preserve">
|
||||
<source>Invalid response</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid server address!" xml:space="preserve">
|
||||
<source>Invalid server address!</source>
|
||||
<target>Neplatná adresa serveru!</target>
|
||||
@@ -3009,6 +3075,10 @@ This cannot be undone!</source>
|
||||
<target>Připojit ke skupině</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Join group conversations" xml:space="preserve">
|
||||
<source>Join group conversations</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Join group?" xml:space="preserve">
|
||||
<source>Join group?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -3032,10 +3102,18 @@ This is your link for group %@!</source>
|
||||
<target>Připojování ke skupině</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Keep" xml:space="preserve">
|
||||
<source>Keep</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Keep the app open to use it from desktop" xml:space="preserve">
|
||||
<source>Keep the app open to use it from desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Keep unused invitation?" xml:space="preserve">
|
||||
<source>Keep unused invitation?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Keep your connections" xml:space="preserve">
|
||||
<source>Keep your connections</source>
|
||||
<target>Zachovat vaše připojení</target>
|
||||
@@ -3118,6 +3196,11 @@ This is your link for group %@!</source>
|
||||
<target>Živé zprávy</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Local" xml:space="preserve">
|
||||
<source>Local</source>
|
||||
<target>Místní</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Local name" xml:space="preserve">
|
||||
<source>Local name</source>
|
||||
<target>Místní název</target>
|
||||
@@ -3357,6 +3440,10 @@ This is your link for group %@!</source>
|
||||
<target>Nové heslo</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="New chat" xml:space="preserve">
|
||||
<source>New chat</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="New contact request" xml:space="preserve">
|
||||
<source>New contact request</source>
|
||||
<target>Žádost o nový kontakt</target>
|
||||
@@ -3480,16 +3567,15 @@ This is your link for group %@!</source>
|
||||
- zakázat členy (role "pozorovatel")</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="OK" xml:space="preserve">
|
||||
<source>OK</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Off" xml:space="preserve">
|
||||
<source>Off</source>
|
||||
<target>Vypnout</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Off (Local)" xml:space="preserve">
|
||||
<source>Off (Local)</source>
|
||||
<target>Vypnuto (místní)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Ok" xml:space="preserve">
|
||||
<source>Ok</source>
|
||||
<target>Ok</target>
|
||||
@@ -3550,9 +3636,9 @@ This is your link for group %@!</source>
|
||||
<target>Reakce na zprávy můžete přidávat pouze vy.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Only you can irreversibly delete messages (your contact can mark them for deletion)." xml:space="preserve">
|
||||
<source>Only you can irreversibly delete messages (your contact can mark them for deletion).</source>
|
||||
<target>Nevratně mazat zprávy můžete pouze vy (váš kontakt je může označit ke smazání).</target>
|
||||
<trans-unit id="Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)" xml:space="preserve">
|
||||
<source>Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)</source>
|
||||
<target>Nevratně mazat zprávy můžete pouze vy (váš kontakt je může označit ke smazání). (24 hodin)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Only you can make calls." xml:space="preserve">
|
||||
@@ -3575,9 +3661,9 @@ This is your link for group %@!</source>
|
||||
<target>Reakce na zprávy může přidávat pouze váš kontakt.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Only your contact can irreversibly delete messages (you can mark them for deletion)." xml:space="preserve">
|
||||
<source>Only your contact can irreversibly delete messages (you can mark them for deletion).</source>
|
||||
<target>Nevratně mazat zprávy může pouze váš kontakt (vy je můžete označit ke smazání).</target>
|
||||
<trans-unit id="Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours)" xml:space="preserve">
|
||||
<source>Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours)</source>
|
||||
<target>Nevratně mazat zprávy může pouze váš kontakt (vy je můžete označit ke smazání). (24 hodin)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Only your contact can make calls." xml:space="preserve">
|
||||
@@ -3629,9 +3715,16 @@ This is your link for group %@!</source>
|
||||
<target>Protokol a kód s otevřeným zdrojovým kódem - servery může provozovat kdokoli.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Opening database…" xml:space="preserve">
|
||||
<source>Opening database…</source>
|
||||
<target>Otvírání databáze…</target>
|
||||
<trans-unit id="Opening app…" xml:space="preserve">
|
||||
<source>Opening app…</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Or scan QR code" xml:space="preserve">
|
||||
<source>Or scan QR code</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Or show this code" xml:space="preserve">
|
||||
<source>Or show this code</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="PING count" xml:space="preserve">
|
||||
@@ -3674,10 +3767,9 @@ This is your link for group %@!</source>
|
||||
<target>Heslo k zobrazení</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste" xml:space="preserve">
|
||||
<source>Paste</source>
|
||||
<target>Vložit</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<trans-unit id="Past member %@" xml:space="preserve">
|
||||
<source>Past member %@</source>
|
||||
<note>past/unknown group member</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste desktop address" xml:space="preserve">
|
||||
<source>Paste desktop address</source>
|
||||
@@ -3688,15 +3780,13 @@ This is your link for group %@!</source>
|
||||
<target>Vložit obrázek</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste received link" xml:space="preserve">
|
||||
<source>Paste received link</source>
|
||||
<target>Vložení přijatého odkazu</target>
|
||||
<trans-unit id="Paste link to connect!" xml:space="preserve">
|
||||
<source>Paste link to connect!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste the link you received to connect with your contact." xml:space="preserve">
|
||||
<source>Paste the link you received to connect with your contact.</source>
|
||||
<target>Vložte odkaz, který jste obdrželi, do pole níže a spojte se se svým kontaktem.</target>
|
||||
<note>placeholder</note>
|
||||
<trans-unit id="Paste the link you received" xml:space="preserve">
|
||||
<source>Paste the link you received</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="People can connect to you only via the links you share." xml:space="preserve">
|
||||
<source>People can connect to you only via the links you share.</source>
|
||||
@@ -3733,6 +3823,11 @@ This is your link for group %@!</source>
|
||||
<target>Zkontrolujte prosím nastavení své i svého kontaktu.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please contact developers. Error: %@" xml:space="preserve">
|
||||
<source>Please contact developers.
|
||||
Error: %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please contact group admin." xml:space="preserve">
|
||||
<source>Please contact group admin.</source>
|
||||
<target>Kontaktujte prosím správce skupiny.</target>
|
||||
@@ -3818,6 +3913,10 @@ This is your link for group %@!</source>
|
||||
<target>Soukromé názvy souborů</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Private notes" xml:space="preserve">
|
||||
<source>Private notes</source>
|
||||
<note>name of notes to self</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Profile and server connections" xml:space="preserve">
|
||||
<source>Profile and server connections</source>
|
||||
<target>Profil a připojení k serveru</target>
|
||||
@@ -3936,6 +4035,10 @@ This is your link for group %@!</source>
|
||||
<target>Další informace naleznete v [Uživatelské příručce](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address).</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." xml:space="preserve">
|
||||
<source>Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode).</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." xml:space="preserve">
|
||||
<source>Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends).</source>
|
||||
<target>Přečtěte si více v [Uživatelské příručce](https://simplex.chat/docs/guide/readme.html#connect-to-friends).</target>
|
||||
@@ -3991,6 +4094,10 @@ This is your link for group %@!</source>
|
||||
<target>Příjem přes</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Recent history and improved [directory bot](simplex:/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)." xml:space="preserve">
|
||||
<source>Recent history and improved [directory bot](simplex:/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).</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Recipients see updates as you type them." xml:space="preserve">
|
||||
<source>Recipients see updates as you type them.</source>
|
||||
<target>Příjemci uvidí aktualizace během jejich psaní.</target>
|
||||
@@ -4144,6 +4251,10 @@ This is your link for group %@!</source>
|
||||
<target>Chyba obnovení databáze</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Retry" xml:space="preserve">
|
||||
<source>Retry</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reveal" xml:space="preserve">
|
||||
<source>Reveal</source>
|
||||
<target>Odhalit</target>
|
||||
@@ -4269,6 +4380,10 @@ This is your link for group %@!</source>
|
||||
<target>Uložené servery WebRTC ICE budou odstraněny</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Saved message" xml:space="preserve">
|
||||
<source>Saved message</source>
|
||||
<note>message info title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Scan QR code" xml:space="preserve">
|
||||
<source>Scan QR code</source>
|
||||
<target>Skenovat QR kód</target>
|
||||
@@ -4298,6 +4413,14 @@ This is your link for group %@!</source>
|
||||
<target>Hledat</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search bar accepts invitation links." xml:space="preserve">
|
||||
<source>Search bar accepts invitation 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="Secure queue" xml:space="preserve">
|
||||
<source>Secure queue</source>
|
||||
<target>Zabezpečit frontu</target>
|
||||
@@ -4403,6 +4526,10 @@ This is your link for group %@!</source>
|
||||
<target>Odeslat je z galerie nebo vlastní klávesnice.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Send up to 100 last messages to new members." xml:space="preserve">
|
||||
<source>Send up to 100 last messages to new members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sender cancelled file transfer." xml:space="preserve">
|
||||
<source>Sender cancelled file transfer.</source>
|
||||
<target>Odesílatel zrušil přenos souboru.</target>
|
||||
@@ -4572,9 +4699,8 @@ This is your link for group %@!</source>
|
||||
<target>Sdílet odkaz</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share one-time invitation link" xml:space="preserve">
|
||||
<source>Share one-time invitation link</source>
|
||||
<target>Jednorázový zvací odkaz</target>
|
||||
<trans-unit id="Share this 1-time invite link" xml:space="preserve">
|
||||
<source>Share this 1-time invite link</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share with contacts" xml:space="preserve">
|
||||
@@ -4697,16 +4823,15 @@ This is your link for group %@!</source>
|
||||
<target>Někdo</target>
|
||||
<note>notification title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Start a new chat" xml:space="preserve">
|
||||
<source>Start a new chat</source>
|
||||
<target>Začít nový chat</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Start chat" xml:space="preserve">
|
||||
<source>Start chat</source>
|
||||
<target>Začít chat</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Start chat?" xml:space="preserve">
|
||||
<source>Start chat?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Start migration" xml:space="preserve">
|
||||
<source>Start migration</source>
|
||||
<target>Zahájit přenesení</target>
|
||||
@@ -4831,6 +4956,14 @@ This is your link for group %@!</source>
|
||||
<target>Klepnutím se připojíte inkognito</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to paste link" xml:space="preserve">
|
||||
<source>Tap to paste link</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to scan" xml:space="preserve">
|
||||
<source>Tap to scan</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to start a new chat" xml:space="preserve">
|
||||
<source>Tap to start a new chat</source>
|
||||
<target>Klepnutím na zahájíte nový chat</target>
|
||||
@@ -4893,6 +5026,10 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován
|
||||
<target>Pokus o změnu přístupové fráze databáze nebyl dokončen.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The code you scanned is not a SimpleX link QR code." xml:space="preserve">
|
||||
<source>The code you scanned is not a SimpleX link QR code.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The connection you accepted will be cancelled!" xml:space="preserve">
|
||||
<source>The connection you accepted will be cancelled!</source>
|
||||
<target>Připojení, které jste přijali, bude zrušeno!</target>
|
||||
@@ -4958,21 +5095,15 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován
|
||||
<target>Servery pro nová připojení vašeho aktuálního chat profilu **%@**.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The text you pasted is not a SimpleX link." xml:space="preserve">
|
||||
<source>The text you pasted is not a SimpleX link.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Theme" xml:space="preserve">
|
||||
<source>Theme</source>
|
||||
<target>Téma</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="There should be at least one user profile." xml:space="preserve">
|
||||
<source>There should be at least one user profile.</source>
|
||||
<target>Měl by tam být alespoň jeden uživatelský profil.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="There should be at least one visible user profile." xml:space="preserve">
|
||||
<source>There should be at least one visible user profile.</source>
|
||||
<target>Měl by tam být alespoň jeden viditelný uživatelský profil.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="These settings are for your current profile **%@**." xml:space="preserve">
|
||||
<source>These settings are for your current profile **%@**.</source>
|
||||
<target>Toto nastavení je pro váš aktuální profil **%@**.</target>
|
||||
@@ -5002,6 +5133,10 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován
|
||||
<source>This device name</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This display name is invalid. Please choose another name." xml:space="preserve">
|
||||
<source>This display name is invalid. Please choose another name.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This group has over %lld members, delivery receipts are not sent." xml:space="preserve">
|
||||
<source>This group has over %lld members, delivery receipts are not sent.</source>
|
||||
<target>Tato skupina má více než %lld členů, potvrzení o doručení nejsou odesílány.</target>
|
||||
@@ -5101,16 +5236,15 @@ Před zapnutím této funkce budete vyzváni k dokončení ověření.</target>
|
||||
<target>Pokus o připojení k serveru používanému pro příjem zpráv od tohoto kontaktu.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Turkish interface" xml:space="preserve">
|
||||
<source>Turkish interface</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Turn off" xml:space="preserve">
|
||||
<source>Turn off</source>
|
||||
<target>Vypnout</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Turn off notifications?" xml:space="preserve">
|
||||
<source>Turn off notifications?</source>
|
||||
<target>Vypnout upozornění?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Turn on" xml:space="preserve">
|
||||
<source>Turn on</source>
|
||||
<target>Zapnout</target>
|
||||
@@ -5125,10 +5259,18 @@ Před zapnutím této funkce budete vyzváni k dokončení ověření.</target>
|
||||
<source>Unblock</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unblock for all" xml:space="preserve">
|
||||
<source>Unblock for all</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unblock member" xml:space="preserve">
|
||||
<source>Unblock member</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unblock member for all?" xml:space="preserve">
|
||||
<source>Unblock member for all?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unblock member?" xml:space="preserve">
|
||||
<source>Unblock member?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -5223,6 +5365,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
|
||||
<target>Nepřečtený</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Up to 100 last messages are sent to new members." xml:space="preserve">
|
||||
<source>Up to 100 last messages are sent to new members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Update" xml:space="preserve">
|
||||
<source>Update</source>
|
||||
<target>Aktualizovat</target>
|
||||
@@ -5307,6 +5453,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
|
||||
<target>Použít nový inkognito profil</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Use only local notifications?" xml:space="preserve">
|
||||
<source>Use only local notifications?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Use server" xml:space="preserve">
|
||||
<source>Use server</source>
|
||||
<target>Použít server</target>
|
||||
@@ -5383,6 +5533,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
|
||||
<target>Zobrazení bezpečnostního kódu</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Visible history" xml:space="preserve">
|
||||
<source>Visible history</source>
|
||||
<note>chat feature</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Voice messages" xml:space="preserve">
|
||||
<source>Voice messages</source>
|
||||
<target>Hlasové zprávy</target>
|
||||
@@ -5467,11 +5621,19 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu
|
||||
<target>Pokud s někým sdílíte inkognito profil, bude tento profil použit pro skupiny, do kterých vás pozve.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="With encrypted files and media." xml:space="preserve">
|
||||
<source>With encrypted files and media.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="With optional welcome message." xml:space="preserve">
|
||||
<source>With optional welcome message.</source>
|
||||
<target>S volitelnou uvítací zprávou.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="With reduced battery usage." xml:space="preserve">
|
||||
<source>With reduced battery usage.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Wrong database passphrase" xml:space="preserve">
|
||||
<source>Wrong database passphrase</source>
|
||||
<target>Špatná přístupová fráze k databázi</target>
|
||||
@@ -5556,11 +5718,6 @@ Repeat join request?</source>
|
||||
<target>Můžete přijímat hovory z obrazovky zámku, bez ověření zařízení a aplikace.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button." xml:space="preserve">
|
||||
<source>You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button.</source>
|
||||
<target>Můžete se také připojit kliknutím na odkaz. Pokud se otevře v prohlížeči, klikněte na tlačítko **Otevřít v mobilní aplikaci**.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can create it later" xml:space="preserve">
|
||||
<source>You can create it later</source>
|
||||
<target>Můžete vytvořit později</target>
|
||||
@@ -5581,6 +5738,10 @@ Repeat join request?</source>
|
||||
<target>Profil uživatele můžete skrýt nebo ztlumit - přejeďte prstem doprava.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can make it visible to your SimpleX contacts via Settings." xml:space="preserve">
|
||||
<source>You can make it visible to your SimpleX contacts via Settings.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can now send messages to %@" xml:space="preserve">
|
||||
<source>You can now send messages to %@</source>
|
||||
<target>Nyní můžete posílat zprávy %@</target>
|
||||
@@ -5621,6 +5782,10 @@ Repeat join request?</source>
|
||||
<target>K formátování zpráv můžete použít markdown:</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can view invitation link again in connection details." xml:space="preserve">
|
||||
<source>You can view invitation link again in connection details.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can't send messages!" xml:space="preserve">
|
||||
<source>You can't send messages!</source>
|
||||
<target>Nemůžete posílat zprávy!</target>
|
||||
@@ -5805,13 +5970,6 @@ Toto připojení můžete zrušit a kontakt odebrat (a zkusit to později s nov
|
||||
<target>Vaše kontakty mohou povolit úplné mazání zpráv.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your contacts in SimpleX will see it. You can change it in Settings." xml:space="preserve">
|
||||
<source>Your contacts in SimpleX will see it.
|
||||
You can change it in Settings.</source>
|
||||
<target>Vaše kontakty v SimpleX ji uvidí.
|
||||
Můžete ji změnit v Nastavení.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your contacts will remain connected." xml:space="preserve">
|
||||
<source>Your contacts will remain connected.</source>
|
||||
<target>Vaše kontakty zůstanou připojeny.</target>
|
||||
@@ -5960,6 +6118,14 @@ Servery SimpleX nevidí váš profil.</target>
|
||||
<source>blocked</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="blocked %@" xml:space="preserve">
|
||||
<source>blocked %@</source>
|
||||
<note>rcv group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="blocked by admin" xml:space="preserve">
|
||||
<source>blocked by admin</source>
|
||||
<note>blocked chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="bold" xml:space="preserve">
|
||||
<source>bold</source>
|
||||
<target>tučně</target>
|
||||
@@ -6080,6 +6246,10 @@ Servery SimpleX nevidí váš profil.</target>
|
||||
<target>připojení:%@</target>
|
||||
<note>connection information</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="contact %@ changed to %@" xml:space="preserve">
|
||||
<source>contact %1$@ changed to %2$@</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="contact has e2e encryption" xml:space="preserve">
|
||||
<source>contact has e2e encryption</source>
|
||||
<target>kontakt má šifrování e2e</target>
|
||||
@@ -6348,6 +6518,10 @@ Servery SimpleX nevidí váš profil.</target>
|
||||
<target>člen</target>
|
||||
<note>member role</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="member %@ changed to %@" xml:space="preserve">
|
||||
<source>member %1$@ changed to %2$@</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="member connected" xml:space="preserve">
|
||||
<source>connected</source>
|
||||
<target>připojeno</target>
|
||||
@@ -6470,6 +6644,14 @@ Servery SimpleX nevidí váš profil.</target>
|
||||
<target>odstraněno %@</target>
|
||||
<note>rcv group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="removed contact address" xml:space="preserve">
|
||||
<source>removed contact address</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="removed profile picture" xml:space="preserve">
|
||||
<source>removed profile picture</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="removed you" xml:space="preserve">
|
||||
<source>removed you</source>
|
||||
<target>odstranil vás</target>
|
||||
@@ -6500,6 +6682,14 @@ Servery SimpleX nevidí váš profil.</target>
|
||||
<target>odeslat přímou zprávu</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="set new contact address" xml:space="preserve">
|
||||
<source>set new contact address</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="set new profile picture" xml:space="preserve">
|
||||
<source>set new profile picture</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="starting…" xml:space="preserve">
|
||||
<source>starting…</source>
|
||||
<target>začíná…</target>
|
||||
@@ -6515,16 +6705,28 @@ Servery SimpleX nevidí váš profil.</target>
|
||||
<target>tento kontakt</target>
|
||||
<note>notification title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="unblocked %@" xml:space="preserve">
|
||||
<source>unblocked %@</source>
|
||||
<note>rcv group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="unknown" xml:space="preserve">
|
||||
<source>unknown</source>
|
||||
<target>neznámý</target>
|
||||
<note>connection info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="unknown status" xml:space="preserve">
|
||||
<source>unknown status</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="updated group profile" xml:space="preserve">
|
||||
<source>updated group profile</source>
|
||||
<target>aktualizoval profil skupiny</target>
|
||||
<note>rcv group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="updated profile" xml:space="preserve">
|
||||
<source>updated profile</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="v%@" xml:space="preserve">
|
||||
<source>v%@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -6594,6 +6796,10 @@ Servery SimpleX nevidí váš profil.</target>
|
||||
<target>jste pozorovatel</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="you blocked %@" xml:space="preserve">
|
||||
<source>you blocked %@</source>
|
||||
<note>snd group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="you changed address" xml:space="preserve">
|
||||
<source>you changed address</source>
|
||||
<target>změnili jste adresu</target>
|
||||
@@ -6634,6 +6840,10 @@ Servery SimpleX nevidí váš profil.</target>
|
||||
<target>sdíleli jste jednorázový odkaz inkognito</target>
|
||||
<note>chat list item description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="you unblocked %@" xml:space="preserve">
|
||||
<source>you unblocked %@</source>
|
||||
<note>snd group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="you: " xml:space="preserve">
|
||||
<source>you: </source>
|
||||
<target>vy: </target>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -31,48 +31,59 @@ Available in v5.1</source>
|
||||
<source> (</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id=" (can be copied)" xml:space="preserve">
|
||||
<trans-unit id=" (can be copied)" xml:space="preserve" approved="no">
|
||||
<source> (can be copied)</source>
|
||||
<target state="translated"> (μπορεί να αντιγραφή)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="!1 colored!" xml:space="preserve">
|
||||
<trans-unit id="!1 colored!" xml:space="preserve" approved="no">
|
||||
<source>!1 colored!</source>
|
||||
<target state="translated">!1 έγχρωμο!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="#secret#" xml:space="preserve">
|
||||
<trans-unit id="#secret#" xml:space="preserve" approved="no">
|
||||
<source>#secret#</source>
|
||||
<target state="translated">#μυστικό#</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@" xml:space="preserve">
|
||||
<trans-unit id="%@" xml:space="preserve" approved="no">
|
||||
<source>%@</source>
|
||||
<target state="translated">%@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ %@" xml:space="preserve">
|
||||
<trans-unit id="%@ %@" xml:space="preserve" approved="no">
|
||||
<source>%@ %@</source>
|
||||
<target state="translated">%@ %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ / %@" xml:space="preserve">
|
||||
<trans-unit id="%@ / %@" xml:space="preserve" approved="no">
|
||||
<source>%@ / %@</source>
|
||||
<target state="translated">%@ / %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ is connected!" xml:space="preserve">
|
||||
<trans-unit id="%@ is connected!" xml:space="preserve" approved="no">
|
||||
<source>%@ is connected!</source>
|
||||
<target state="translated">%@ είναι συνδεδεμένο!</target>
|
||||
<note>notification title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ is not verified" xml:space="preserve">
|
||||
<trans-unit id="%@ is not verified" xml:space="preserve" approved="no">
|
||||
<source>%@ is not verified</source>
|
||||
<target state="translated">%@ δεν είναι επαληθευμένο</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ is verified" xml:space="preserve">
|
||||
<trans-unit id="%@ is verified" xml:space="preserve" approved="no">
|
||||
<source>%@ is verified</source>
|
||||
<target state="translated">%@ είναι επαληθευμένο</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ servers" xml:space="preserve">
|
||||
<trans-unit id="%@ servers" xml:space="preserve" approved="no">
|
||||
<source>%@ servers</source>
|
||||
<target state="translated">%@ διακομιστές</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ wants to connect!" xml:space="preserve">
|
||||
<trans-unit id="%@ wants to connect!" xml:space="preserve" approved="no">
|
||||
<source>%@ wants to connect!</source>
|
||||
<target state="translated">%@ θέλει να συνδεθεί!</target>
|
||||
<note>notification title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%d days" xml:space="preserve">
|
||||
@@ -4162,6 +4173,66 @@ SimpleX servers cannot see your profile.</source>
|
||||
<source>\~strike~</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ connected" xml:space="preserve" approved="no">
|
||||
<source>%@ connected</source>
|
||||
<target state="translated">%@ συνδεδεμένο</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="# %@" xml:space="preserve" approved="no">
|
||||
<source># %@</source>
|
||||
<target state="translated"># %@</target>
|
||||
<note>copied message info title, # <title></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ and %@" xml:space="preserve" approved="no">
|
||||
<source>%@ and %@</source>
|
||||
<target state="translated">%@ και %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ at %@:" xml:space="preserve" approved="no">
|
||||
<source>%1$@ at %2$@:</source>
|
||||
<target state="translated">%1$@ στις %2$@:</target>
|
||||
<note>copied message info, <sender> at <time></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="## History" xml:space="preserve" approved="no">
|
||||
<source>## History</source>
|
||||
<target state="translated">## Ιστορικό</target>
|
||||
<note>copied message info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="## In reply to" xml:space="preserve" approved="no">
|
||||
<source>## In reply to</source>
|
||||
<target state="translated">## Ως απαντηση σε</target>
|
||||
<note>copied message info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ (current)" xml:space="preserve" approved="no">
|
||||
<source>%@ (current)</source>
|
||||
<target state="translated">%@ (τωρινό)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ (current):" xml:space="preserve" approved="no">
|
||||
<source>%@ (current):</source>
|
||||
<target state="translated">%@ (τωρινό):</target>
|
||||
<note>copied message info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ and %@ connected" xml:space="preserve" approved="no">
|
||||
<source>%@ and %@ connected</source>
|
||||
<target state="translated">%@ και %@ συνδεδεμένο</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@:" xml:space="preserve" approved="no">
|
||||
<source>%@:</source>
|
||||
<target state="translated">%@:</target>
|
||||
<note>copied message info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@, %@ and %lld members" xml:space="preserve" approved="no">
|
||||
<source>%@, %@ and %lld members</source>
|
||||
<target state="translated">%@, %@ και %lld μέλη</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@, %@ and %lld other members connected" xml:space="preserve" approved="no">
|
||||
<source>%@, %@ and %lld other members connected</source>
|
||||
<target state="translated">%@, %@ και %lld άλλα μέλη συνδέθηκαν</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="el" datatype="plaintext">
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -212,6 +212,10 @@
|
||||
<source>%lld messages blocked</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld messages blocked by admin" xml:space="preserve">
|
||||
<source>%lld messages blocked by admin</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld messages marked deleted" xml:space="preserve">
|
||||
<source>%lld messages marked deleted</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -303,14 +307,17 @@
|
||||
<target>)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Add contact**: to create a new invitation link, or connect via a link you received." xml:space="preserve">
|
||||
<source>**Add contact**: to create a new invitation link, or connect via a link you received.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Add new contact**: to create your one-time QR Code for your contact." xml:space="preserve">
|
||||
<source>**Add new contact**: to create your one-time QR Code or link for your contact.</source>
|
||||
<target>**Lisää uusi kontakti**: luo kertakäyttöinen QR-koodi tai linkki kontaktille.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Create link / QR code** for your contact to use." xml:space="preserve">
|
||||
<source>**Create link / QR code** for your contact to use.</source>
|
||||
<target>**Luo linkki / QR-koodi* kontaktille.</target>
|
||||
<trans-unit id="**Create group**: to create a new group." xml:space="preserve">
|
||||
<source>**Create group**: to create a new group.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." xml:space="preserve">
|
||||
@@ -323,11 +330,6 @@
|
||||
<target>**Yksityisin**: älä käytä SimpleX Chat -ilmoituspalvelinta, tarkista viestit ajoittain taustalla (riippuu siitä, kuinka usein käytät sovellusta).</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Paste received link** or open it in the browser and tap **Open in mobile app**." xml:space="preserve">
|
||||
<source>**Paste received link** or open it in the browser and tap **Open in mobile app**.</source>
|
||||
<target>**Liitä vastaanotettu linkki** tai avaa se selaimessa ja napauta **Avaa mobiilisovelluksessa**.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Please note**: you will NOT be able to recover or change passphrase if you lose it." xml:space="preserve">
|
||||
<source>**Please note**: you will NOT be able to recover or change passphrase if you lose it.</source>
|
||||
<target>**Huomaa**: et voi palauttaa tai muuttaa tunnuslausetta, jos kadotat sen.</target>
|
||||
@@ -338,11 +340,6 @@
|
||||
<target>**Suositus**: laitetunnus ja ilmoitukset lähetetään SimpleX Chat -ilmoituspalvelimelle, mutta ei viestin sisältöä, kokoa tai sitä, keneltä se on peräisin.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Scan QR code**: to connect to your contact in person or via video call." xml:space="preserve">
|
||||
<source>**Scan QR code**: to connect to your contact in person or via video call.</source>
|
||||
<target>**Skannaa QR-koodi**: muodosta yhteys kontaktiisi henkilökohtaisesti tai videopuhelun kautta.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Warning**: Instant push notifications require passphrase saved in Keychain." xml:space="preserve">
|
||||
<source>**Warning**: Instant push notifications require passphrase saved in Keychain.</source>
|
||||
<target>**Varoitus**: Välittömät push-ilmoitukset vaativat tunnuslauseen, joka on tallennettu Keychainiin.</target>
|
||||
@@ -437,11 +434,6 @@
|
||||
<target>1 viikko</target>
|
||||
<note>time interval</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="1-time link" xml:space="preserve">
|
||||
<source>1-time link</source>
|
||||
<target>Kertakäyttölinkki</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="5 minutes" xml:space="preserve">
|
||||
<source>5 minutes</source>
|
||||
<target>5 minuuttia</target>
|
||||
@@ -557,6 +549,10 @@
|
||||
<target>Lisää osoite profiiliisi, jotta kontaktisi voivat jakaa sen muiden kanssa. Profiilipäivitys lähetetään kontakteillesi.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add contact" xml:space="preserve">
|
||||
<source>Add contact</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add preset servers" xml:space="preserve">
|
||||
<source>Add preset servers</source>
|
||||
<target>Lisää esiasetettuja palvelimia</target>
|
||||
@@ -627,6 +623,10 @@
|
||||
<target>Kaikki ryhmän jäsenet pysyvät yhteydessä.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages will be deleted - this cannot be undone!" xml:space="preserve">
|
||||
<source>All messages will be deleted - this cannot be undone!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." xml:space="preserve">
|
||||
<source>All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you.</source>
|
||||
<target>Kaikki viestit poistetaan - tätä ei voi kumota! Viestit poistuvat VAIN sinulta.</target>
|
||||
@@ -661,9 +661,9 @@
|
||||
<target>Salli katoavat viestit vain, jos kontaktisi sallii sen sinulle.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow irreversible message deletion only if your contact allows it to you." xml:space="preserve">
|
||||
<source>Allow irreversible message deletion only if your contact allows it to you.</source>
|
||||
<target>Salli peruuttamaton viestien poisto vain, jos kontaktisi sallii ne sinulle.</target>
|
||||
<trans-unit id="Allow irreversible message deletion only if your contact allows it to you. (24 hours)" xml:space="preserve">
|
||||
<source>Allow irreversible message deletion only if your contact allows it to you. (24 hours)</source>
|
||||
<target>Salli peruuttamaton viestien poisto vain, jos kontaktisi sallii ne sinulle. (24 tuntia)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow message reactions only if your contact allows them." xml:space="preserve">
|
||||
@@ -686,9 +686,9 @@
|
||||
<target>Salli katoavien viestien lähettäminen.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow to irreversibly delete sent messages." xml:space="preserve">
|
||||
<source>Allow to irreversibly delete sent messages.</source>
|
||||
<target>Salli lähetettyjen viestien peruuttamaton poistaminen.</target>
|
||||
<trans-unit id="Allow to irreversibly delete sent messages. (24 hours)" xml:space="preserve">
|
||||
<source>Allow to irreversibly delete sent messages. (24 hours)</source>
|
||||
<target>Salli lähetettyjen viestien peruuttamaton poistaminen. (24 tuntia)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow to send files and media." xml:space="preserve">
|
||||
@@ -721,9 +721,9 @@
|
||||
<target>Salli kontaktiesi soittaa sinulle.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow your contacts to irreversibly delete sent messages." xml:space="preserve">
|
||||
<source>Allow your contacts to irreversibly delete sent messages.</source>
|
||||
<target>Salli kontaktiesi poistaa lähetetyt viestit peruuttamattomasti.</target>
|
||||
<trans-unit id="Allow your contacts to irreversibly delete sent messages. (24 hours)" xml:space="preserve">
|
||||
<source>Allow your contacts to irreversibly delete sent messages. (24 hours)</source>
|
||||
<target>Salli kontaktiesi poistaa lähetetyt viestit peruuttamattomasti. (24 tuntia)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow your contacts to send disappearing messages." xml:space="preserve">
|
||||
@@ -895,6 +895,10 @@
|
||||
<source>Block</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Block for all" xml:space="preserve">
|
||||
<source>Block for all</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Block group members" xml:space="preserve">
|
||||
<source>Block group members</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -903,18 +907,26 @@
|
||||
<source>Block member</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Block member for all?" xml:space="preserve">
|
||||
<source>Block member for all?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Block member?" xml:space="preserve">
|
||||
<source>Block member?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Blocked by admin" xml:space="preserve">
|
||||
<source>Blocked by admin</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
|
||||
<source>Both you and your contact can add message reactions.</source>
|
||||
<target>Sekä sinä että kontaktisi voivat käyttää viestireaktioita.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Both you and your contact can irreversibly delete sent messages." xml:space="preserve">
|
||||
<source>Both you and your contact can irreversibly delete sent messages.</source>
|
||||
<target>Sekä sinä että kontaktisi voitte peruuttamattomasti poistaa lähetetyt viestit.</target>
|
||||
<trans-unit id="Both you and your contact can irreversibly delete sent messages. (24 hours)" xml:space="preserve">
|
||||
<source>Both you and your contact can irreversibly delete sent messages. (24 hours)</source>
|
||||
<target>Sekä sinä että kontaktisi voitte peruuttamattomasti poistaa lähetetyt viestit. (24 tuntia)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Both you and your contact can make calls." xml:space="preserve">
|
||||
@@ -951,9 +963,8 @@
|
||||
<target>Puhelut</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Can't delete user profile!" xml:space="preserve">
|
||||
<source>Can't delete user profile!</source>
|
||||
<target>Käyttäjäprofiilia ei voi poistaa!</target>
|
||||
<trans-unit id="Camera not available" xml:space="preserve">
|
||||
<source>Camera not available</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Can't invite contact!" xml:space="preserve">
|
||||
@@ -1067,6 +1078,10 @@
|
||||
<target>Chat on pysäytetty</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." xml:space="preserve">
|
||||
<source>Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat preferences" xml:space="preserve">
|
||||
<source>Chat preferences</source>
|
||||
<target>Chat-asetukset</target>
|
||||
@@ -1112,6 +1127,10 @@
|
||||
<target>Tyhjennä keskustelu?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Clear private notes?" xml:space="preserve">
|
||||
<source>Clear private notes?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Clear verification" xml:space="preserve">
|
||||
<source>Clear verification</source>
|
||||
<target>Tyhjennä vahvistus</target>
|
||||
@@ -1203,11 +1222,6 @@ This is your own one-time link!</source>
|
||||
<target>Yhdistä linkin kautta</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via link / QR code" xml:space="preserve">
|
||||
<source>Connect via link / QR code</source>
|
||||
<target>Yhdistä linkillä / QR-koodilla</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via one-time link" xml:space="preserve">
|
||||
<source>Connect via one-time link</source>
|
||||
<target>Yhdistä kertalinkillä</target>
|
||||
@@ -1375,11 +1389,6 @@ This is your own one-time link!</source>
|
||||
<target>Luo uusi profiili [työpöytäsovelluksessa](https://simplex.chat/downloads/). 💻</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create one-time invitation link" xml:space="preserve">
|
||||
<source>Create one-time invitation link</source>
|
||||
<target>Luo kertakutsulinkki</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create profile" xml:space="preserve">
|
||||
<source>Create profile</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -1399,11 +1408,23 @@ This is your own one-time link!</source>
|
||||
<target>Luo profiilisi</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Created at" xml:space="preserve">
|
||||
<source>Created at</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Created at: %@" xml:space="preserve">
|
||||
<source>Created at: %@</source>
|
||||
<note>copied message info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Created on %@" xml:space="preserve">
|
||||
<source>Created on %@</source>
|
||||
<target>Luotu %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Creating link…" xml:space="preserve">
|
||||
<source>Creating link…</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Current Passcode" xml:space="preserve">
|
||||
<source>Current Passcode</source>
|
||||
<target>Nykyinen pääsykoodi</target>
|
||||
@@ -1875,6 +1896,10 @@ This cannot be undone!</source>
|
||||
<target>Tee myöhemmin</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Do not send history to new members." xml:space="preserve">
|
||||
<source>Do not send history to new members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Don't create address" xml:space="preserve">
|
||||
<source>Don't create address</source>
|
||||
<target>Älä luo osoitetta</target>
|
||||
@@ -1945,6 +1970,10 @@ This cannot be undone!</source>
|
||||
<target>Ota automaattinen viestien poisto käyttöön?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enable camera access" xml:space="preserve">
|
||||
<source>Enable camera access</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enable for all" xml:space="preserve">
|
||||
<source>Enable for all</source>
|
||||
<target>Salli kaikille</target>
|
||||
@@ -2009,6 +2038,10 @@ This cannot be undone!</source>
|
||||
<target>Salattu viesti tai muu tapahtuma</target>
|
||||
<note>notification</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Encrypted message: app is stopped" xml:space="preserve">
|
||||
<source>Encrypted message: app is stopped</source>
|
||||
<note>notification</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Encrypted message: database error" xml:space="preserve">
|
||||
<source>Encrypted message: database error</source>
|
||||
<target>Salattu viesti: tietokantavirhe</target>
|
||||
@@ -2148,6 +2181,10 @@ This cannot be undone!</source>
|
||||
<source>Error creating member contact</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error creating message" xml:space="preserve">
|
||||
<source>Error creating message</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error creating profile!" xml:space="preserve">
|
||||
<source>Error creating profile!</source>
|
||||
<target>Virhe profiilin luomisessa!</target>
|
||||
@@ -2233,6 +2270,10 @@ This cannot be undone!</source>
|
||||
<target>Virhe %@-palvelimien lataamisessa</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error opening chat" xml:space="preserve">
|
||||
<source>Error opening chat</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error receiving file" xml:space="preserve">
|
||||
<source>Error receiving file</source>
|
||||
<target>Virhe tiedoston vastaanottamisessa</target>
|
||||
@@ -2273,6 +2314,10 @@ This cannot be undone!</source>
|
||||
<target>Virhe käyttäjän salasanan tallentamisessa</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error scanning code: %@" xml:space="preserve">
|
||||
<source>Error scanning code: %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error sending email" xml:space="preserve">
|
||||
<source>Error sending email</source>
|
||||
<target>Virhe sähköpostin lähettämisessä</target>
|
||||
@@ -2596,9 +2641,9 @@ This cannot be undone!</source>
|
||||
<target>Ryhmän jäsenet voivat lisätä viestireaktioita.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group members can irreversibly delete sent messages." xml:space="preserve">
|
||||
<source>Group members can irreversibly delete sent messages.</source>
|
||||
<target>Ryhmän jäsenet voivat poistaa lähetetyt viestit peruuttamattomasti.</target>
|
||||
<trans-unit id="Group members can irreversibly delete sent messages. (24 hours)" xml:space="preserve">
|
||||
<source>Group members can irreversibly delete sent messages. (24 hours)</source>
|
||||
<target>Ryhmän jäsenet voivat poistaa lähetetyt viestit peruuttamattomasti. (24 tuntia)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group members can send direct messages." xml:space="preserve">
|
||||
@@ -2706,6 +2751,10 @@ This cannot be undone!</source>
|
||||
<target>Historia</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="History is not sent to new members." xml:space="preserve">
|
||||
<source>History is not sent to new members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="How SimpleX works" xml:space="preserve">
|
||||
<source>How SimpleX works</source>
|
||||
<target>Miten SimpleX toimii</target>
|
||||
@@ -2741,11 +2790,6 @@ This cannot be undone!</source>
|
||||
<target>Jos et voi tavata henkilökohtaisesti, näytä QR-koodi videopuhelussa tai jaa linkki.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link." xml:space="preserve">
|
||||
<source>If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link.</source>
|
||||
<target>Jos et voi tavata henkilökohtaisesti, voit **skannata QR-koodin videopuhelussa** tai kontaktisi voi jakaa kutsulinkin.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="If you enter this passcode when opening the app, all app data will be irreversibly removed!" xml:space="preserve">
|
||||
<source>If you enter this passcode when opening the app, all app data will be irreversibly removed!</source>
|
||||
<target>Jos syötät tämän pääsykoodin sovellusta avatessasi, kaikki sovelluksen tiedot poistetaan peruuttamattomasti!</target>
|
||||
@@ -2801,6 +2845,10 @@ This cannot be undone!</source>
|
||||
<target>Tuo tietokanta</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Improved message delivery" xml:space="preserve">
|
||||
<source>Improved message delivery</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Improved privacy and security" xml:space="preserve">
|
||||
<source>Improved privacy and security</source>
|
||||
<target>Parannettu yksityisyys ja turvallisuus</target>
|
||||
@@ -2901,15 +2949,31 @@ This cannot be undone!</source>
|
||||
<target>Käyttöliittymä</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid QR code" xml:space="preserve">
|
||||
<source>Invalid QR code</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid connection link" xml:space="preserve">
|
||||
<source>Invalid connection link</source>
|
||||
<target>Virheellinen yhteyslinkki</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid display name!" xml:space="preserve">
|
||||
<source>Invalid display name!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid link" xml:space="preserve">
|
||||
<source>Invalid link</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid name!" xml:space="preserve">
|
||||
<source>Invalid name!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid response" xml:space="preserve">
|
||||
<source>Invalid response</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid server address!" xml:space="preserve">
|
||||
<source>Invalid server address!</source>
|
||||
<target>Virheellinen palvelinosoite!</target>
|
||||
@@ -3001,6 +3065,10 @@ This cannot be undone!</source>
|
||||
<target>Liity ryhmään</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Join group conversations" xml:space="preserve">
|
||||
<source>Join group conversations</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Join group?" xml:space="preserve">
|
||||
<source>Join group?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -3024,10 +3092,18 @@ This is your link for group %@!</source>
|
||||
<target>Liittyy ryhmään</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Keep" xml:space="preserve">
|
||||
<source>Keep</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Keep the app open to use it from desktop" xml:space="preserve">
|
||||
<source>Keep the app open to use it from desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Keep unused invitation?" xml:space="preserve">
|
||||
<source>Keep unused invitation?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Keep your connections" xml:space="preserve">
|
||||
<source>Keep your connections</source>
|
||||
<target>Pidä kontaktisi</target>
|
||||
@@ -3110,6 +3186,11 @@ This is your link for group %@!</source>
|
||||
<target>Live-viestit</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Local" xml:space="preserve">
|
||||
<source>Local</source>
|
||||
<target>Paikallinen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Local name" xml:space="preserve">
|
||||
<source>Local name</source>
|
||||
<target>Paikallinen nimi</target>
|
||||
@@ -3349,6 +3430,10 @@ This is your link for group %@!</source>
|
||||
<target>Uusi pääsykoodi</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="New chat" xml:space="preserve">
|
||||
<source>New chat</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="New contact request" xml:space="preserve">
|
||||
<source>New contact request</source>
|
||||
<target>Uusi kontaktipyyntö</target>
|
||||
@@ -3471,16 +3556,15 @@ This is your link for group %@!</source>
|
||||
- poista jäsenet käytöstä ("tarkkailija" rooli)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="OK" xml:space="preserve">
|
||||
<source>OK</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Off" xml:space="preserve">
|
||||
<source>Off</source>
|
||||
<target>Pois</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Off (Local)" xml:space="preserve">
|
||||
<source>Off (Local)</source>
|
||||
<target>Pois (Paikallinen)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Ok" xml:space="preserve">
|
||||
<source>Ok</source>
|
||||
<target>Ok</target>
|
||||
@@ -3541,9 +3625,9 @@ This is your link for group %@!</source>
|
||||
<target>Vain sinä voit lisätä viestireaktioita.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Only you can irreversibly delete messages (your contact can mark them for deletion)." xml:space="preserve">
|
||||
<source>Only you can irreversibly delete messages (your contact can mark them for deletion).</source>
|
||||
<target>Vain sinä voit poistaa viestejä peruuttamattomasti (kontaktisi voi merkitä ne poistettavaksi).</target>
|
||||
<trans-unit id="Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)" xml:space="preserve">
|
||||
<source>Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)</source>
|
||||
<target>Vain sinä voit poistaa viestejä peruuttamattomasti (kontaktisi voi merkitä ne poistettavaksi). (24 tuntia)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Only you can make calls." xml:space="preserve">
|
||||
@@ -3566,9 +3650,9 @@ This is your link for group %@!</source>
|
||||
<target>Vain kontaktisi voi lisätä viestireaktioita.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Only your contact can irreversibly delete messages (you can mark them for deletion)." xml:space="preserve">
|
||||
<source>Only your contact can irreversibly delete messages (you can mark them for deletion).</source>
|
||||
<target>Vain kontaktisi voi poistaa viestejä peruuttamattomasti (voit merkitä ne poistettavaksi).</target>
|
||||
<trans-unit id="Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours)" xml:space="preserve">
|
||||
<source>Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours)</source>
|
||||
<target>Vain kontaktisi voi poistaa viestejä peruuttamattomasti (voit merkitä ne poistettavaksi). (24 tuntia)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Only your contact can make calls." xml:space="preserve">
|
||||
@@ -3619,9 +3703,16 @@ This is your link for group %@!</source>
|
||||
<target>Avoimen lähdekoodin protokolla ja koodi - kuka tahansa voi käyttää palvelimia.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Opening database…" xml:space="preserve">
|
||||
<source>Opening database…</source>
|
||||
<target>Avataan tietokantaa…</target>
|
||||
<trans-unit id="Opening app…" xml:space="preserve">
|
||||
<source>Opening app…</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Or scan QR code" xml:space="preserve">
|
||||
<source>Or scan QR code</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Or show this code" xml:space="preserve">
|
||||
<source>Or show this code</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="PING count" xml:space="preserve">
|
||||
@@ -3664,10 +3755,9 @@ This is your link for group %@!</source>
|
||||
<target>Salasana näytettäväksi</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste" xml:space="preserve">
|
||||
<source>Paste</source>
|
||||
<target>Liitä</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<trans-unit id="Past member %@" xml:space="preserve">
|
||||
<source>Past member %@</source>
|
||||
<note>past/unknown group member</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste desktop address" xml:space="preserve">
|
||||
<source>Paste desktop address</source>
|
||||
@@ -3678,15 +3768,13 @@ This is your link for group %@!</source>
|
||||
<target>Liitä kuva</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste received link" xml:space="preserve">
|
||||
<source>Paste received link</source>
|
||||
<target>Liitä vastaanotettu linkki</target>
|
||||
<trans-unit id="Paste link to connect!" xml:space="preserve">
|
||||
<source>Paste link to connect!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste the link you received to connect with your contact." xml:space="preserve">
|
||||
<source>Paste the link you received to connect with your contact.</source>
|
||||
<target>Liitä saamasi linkki, jonka avulla voit muodostaa yhteyden kontaktiisi.</target>
|
||||
<note>placeholder</note>
|
||||
<trans-unit id="Paste the link you received" xml:space="preserve">
|
||||
<source>Paste the link you received</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="People can connect to you only via the links you share." xml:space="preserve">
|
||||
<source>People can connect to you only via the links you share.</source>
|
||||
@@ -3723,6 +3811,11 @@ This is your link for group %@!</source>
|
||||
<target>Tarkista omasi ja kontaktin asetukset.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please contact developers. Error: %@" xml:space="preserve">
|
||||
<source>Please contact developers.
|
||||
Error: %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please contact group admin." xml:space="preserve">
|
||||
<source>Please contact group admin.</source>
|
||||
<target>Ota yhteyttä ryhmän ylläpitäjään.</target>
|
||||
@@ -3808,6 +3901,10 @@ This is your link for group %@!</source>
|
||||
<target>Yksityiset tiedostonimet</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Private notes" xml:space="preserve">
|
||||
<source>Private notes</source>
|
||||
<note>name of notes to self</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Profile and server connections" xml:space="preserve">
|
||||
<source>Profile and server connections</source>
|
||||
<target>Profiili- ja palvelinyhteydet</target>
|
||||
@@ -3926,6 +4023,10 @@ This is your link for group %@!</source>
|
||||
<target>Lue lisää [Käyttöoppaasta](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address).</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." xml:space="preserve">
|
||||
<source>Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode).</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." xml:space="preserve">
|
||||
<source>Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends).</source>
|
||||
<target>Lue lisää [Käyttöoppaasta](https://simplex.chat/docs/guide/readme.html#connect-to-friends).</target>
|
||||
@@ -3981,6 +4082,10 @@ This is your link for group %@!</source>
|
||||
<target>Vastaanotto kautta</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Recent history and improved [directory bot](simplex:/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)." xml:space="preserve">
|
||||
<source>Recent history and improved [directory bot](simplex:/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).</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Recipients see updates as you type them." xml:space="preserve">
|
||||
<source>Recipients see updates as you type them.</source>
|
||||
<target>Vastaanottajat näkevät päivitykset, kun kirjoitat niitä.</target>
|
||||
@@ -4134,6 +4239,10 @@ This is your link for group %@!</source>
|
||||
<target>Virhe tietokannan palauttamisessa</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Retry" xml:space="preserve">
|
||||
<source>Retry</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reveal" xml:space="preserve">
|
||||
<source>Reveal</source>
|
||||
<target>Paljasta</target>
|
||||
@@ -4259,6 +4368,10 @@ This is your link for group %@!</source>
|
||||
<target>Tallennetut WebRTC ICE -palvelimet poistetaan</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Saved message" xml:space="preserve">
|
||||
<source>Saved message</source>
|
||||
<note>message info title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Scan QR code" xml:space="preserve">
|
||||
<source>Scan QR code</source>
|
||||
<target>Skannaa QR-koodi</target>
|
||||
@@ -4288,6 +4401,14 @@ This is your link for group %@!</source>
|
||||
<target>Haku</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search bar accepts invitation links." xml:space="preserve">
|
||||
<source>Search bar accepts invitation 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="Secure queue" xml:space="preserve">
|
||||
<source>Secure queue</source>
|
||||
<target>Turvallinen jono</target>
|
||||
@@ -4392,6 +4513,10 @@ This is your link for group %@!</source>
|
||||
<target>Lähetä ne galleriasta tai mukautetuista näppäimistöistä.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Send up to 100 last messages to new members." xml:space="preserve">
|
||||
<source>Send up to 100 last messages to new members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sender cancelled file transfer." xml:space="preserve">
|
||||
<source>Sender cancelled file transfer.</source>
|
||||
<target>Lähettäjä peruutti tiedoston siirron.</target>
|
||||
@@ -4561,9 +4686,8 @@ This is your link for group %@!</source>
|
||||
<target>Jaa linkki</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share one-time invitation link" xml:space="preserve">
|
||||
<source>Share one-time invitation link</source>
|
||||
<target>Jaa kertakutsulinkki</target>
|
||||
<trans-unit id="Share this 1-time invite link" xml:space="preserve">
|
||||
<source>Share this 1-time invite link</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share with contacts" xml:space="preserve">
|
||||
@@ -4685,16 +4809,15 @@ This is your link for group %@!</source>
|
||||
<target>Joku</target>
|
||||
<note>notification title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Start a new chat" xml:space="preserve">
|
||||
<source>Start a new chat</source>
|
||||
<target>Aloita uusi keskustelu</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Start chat" xml:space="preserve">
|
||||
<source>Start chat</source>
|
||||
<target>Aloita keskustelu</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Start chat?" xml:space="preserve">
|
||||
<source>Start chat?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Start migration" xml:space="preserve">
|
||||
<source>Start migration</source>
|
||||
<target>Aloita siirto</target>
|
||||
@@ -4819,6 +4942,14 @@ This is your link for group %@!</source>
|
||||
<target>Napauta liittyäksesi incognito-tilassa</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to paste link" xml:space="preserve">
|
||||
<source>Tap to paste link</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to scan" xml:space="preserve">
|
||||
<source>Tap to scan</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to start a new chat" xml:space="preserve">
|
||||
<source>Tap to start a new chat</source>
|
||||
<target>Aloita uusi keskustelu napauttamalla</target>
|
||||
@@ -4881,6 +5012,10 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.</t
|
||||
<target>Tietokannan tunnuslauseen muuttamista ei suoritettu loppuun.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The code you scanned is not a SimpleX link QR code." xml:space="preserve">
|
||||
<source>The code you scanned is not a SimpleX link QR code.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The connection you accepted will be cancelled!" xml:space="preserve">
|
||||
<source>The connection you accepted will be cancelled!</source>
|
||||
<target>Hyväksymäsi yhteys peruuntuu!</target>
|
||||
@@ -4946,21 +5081,15 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.</t
|
||||
<target>Palvelimet nykyisen keskusteluprofiilisi uusille yhteyksille **%@**.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The text you pasted is not a SimpleX link." xml:space="preserve">
|
||||
<source>The text you pasted is not a SimpleX link.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Theme" xml:space="preserve">
|
||||
<source>Theme</source>
|
||||
<target>Teema</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="There should be at least one user profile." xml:space="preserve">
|
||||
<source>There should be at least one user profile.</source>
|
||||
<target>Käyttäjäprofiileja tulee olla vähintään yksi.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="There should be at least one visible user profile." xml:space="preserve">
|
||||
<source>There should be at least one visible user profile.</source>
|
||||
<target>Näkyviä käyttäjäprofiileja tulee olla vähintään yksi.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="These settings are for your current profile **%@**." xml:space="preserve">
|
||||
<source>These settings are for your current profile **%@**.</source>
|
||||
<target>Nämä asetukset koskevat nykyistä profiiliasi **%@**.</target>
|
||||
@@ -4990,6 +5119,10 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.</t
|
||||
<source>This device name</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This display name is invalid. Please choose another name." xml:space="preserve">
|
||||
<source>This display name is invalid. Please choose another name.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This group has over %lld members, delivery receipts are not sent." xml:space="preserve">
|
||||
<source>This group has over %lld members, delivery receipts are not sent.</source>
|
||||
<target>Tässä ryhmässä on yli %lld jäsentä, lähetyskuittauksia ei lähetetä.</target>
|
||||
@@ -5088,16 +5221,15 @@ Sinua kehotetaan suorittamaan todennus loppuun, ennen kuin tämä ominaisuus ote
|
||||
<target>Yritetään muodostaa yhteys palvelimeen, jota käytetään viestien vastaanottamiseen tältä kontaktilta.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Turkish interface" xml:space="preserve">
|
||||
<source>Turkish interface</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Turn off" xml:space="preserve">
|
||||
<source>Turn off</source>
|
||||
<target>Sammuta</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Turn off notifications?" xml:space="preserve">
|
||||
<source>Turn off notifications?</source>
|
||||
<target>Kytke ilmoitukset pois päältä?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Turn on" xml:space="preserve">
|
||||
<source>Turn on</source>
|
||||
<target>Kytke päälle</target>
|
||||
@@ -5112,10 +5244,18 @@ Sinua kehotetaan suorittamaan todennus loppuun, ennen kuin tämä ominaisuus ote
|
||||
<source>Unblock</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unblock for all" xml:space="preserve">
|
||||
<source>Unblock for all</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unblock member" xml:space="preserve">
|
||||
<source>Unblock member</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unblock member for all?" xml:space="preserve">
|
||||
<source>Unblock member for all?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unblock member?" xml:space="preserve">
|
||||
<source>Unblock member?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -5210,6 +5350,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
|
||||
<target>Lukematon</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Up to 100 last messages are sent to new members." xml:space="preserve">
|
||||
<source>Up to 100 last messages are sent to new members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Update" xml:space="preserve">
|
||||
<source>Update</source>
|
||||
<target>Päivitä</target>
|
||||
@@ -5294,6 +5438,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
|
||||
<target>Käytä uutta incognito-profiilia</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Use only local notifications?" xml:space="preserve">
|
||||
<source>Use only local notifications?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Use server" xml:space="preserve">
|
||||
<source>Use server</source>
|
||||
<target>Käytä palvelinta</target>
|
||||
@@ -5370,6 +5518,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
|
||||
<target>Näytä turvakoodi</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Visible history" xml:space="preserve">
|
||||
<source>Visible history</source>
|
||||
<note>chat feature</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Voice messages" xml:space="preserve">
|
||||
<source>Voice messages</source>
|
||||
<target>Ääniviestit</target>
|
||||
@@ -5454,11 +5606,19 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
|
||||
<target>Kun jaat inkognitoprofiilin jonkun kanssa, tätä profiilia käytetään ryhmissä, joihin tämä sinut kutsuu.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="With encrypted files and media." xml:space="preserve">
|
||||
<source>With encrypted files and media.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="With optional welcome message." xml:space="preserve">
|
||||
<source>With optional welcome message.</source>
|
||||
<target>Valinnaisella tervetuloviestillä.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="With reduced battery usage." xml:space="preserve">
|
||||
<source>With reduced battery usage.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Wrong database passphrase" xml:space="preserve">
|
||||
<source>Wrong database passphrase</source>
|
||||
<target>Väärä tietokannan tunnuslause</target>
|
||||
@@ -5543,11 +5703,6 @@ Repeat join request?</source>
|
||||
<target>Voit vastaanottaa puheluita lukitusnäytöltä ilman laitteen ja sovelluksen todennusta.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button." xml:space="preserve">
|
||||
<source>You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button.</source>
|
||||
<target>Voit myös muodostaa yhteyden klikkaamalla linkkiä. Jos se avautuu selaimessa, napsauta **Avaa mobiilisovelluksessa**-painiketta.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can create it later" xml:space="preserve">
|
||||
<source>You can create it later</source>
|
||||
<target>Voit luoda sen myöhemmin</target>
|
||||
@@ -5568,6 +5723,10 @@ Repeat join request?</source>
|
||||
<target>Voit piilottaa tai mykistää käyttäjäprofiilin pyyhkäisemällä sitä oikealle.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can make it visible to your SimpleX contacts via Settings." xml:space="preserve">
|
||||
<source>You can make it visible to your SimpleX contacts via Settings.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can now send messages to %@" xml:space="preserve">
|
||||
<source>You can now send messages to %@</source>
|
||||
<target>Voit nyt lähettää viestejä %@:lle</target>
|
||||
@@ -5608,6 +5767,10 @@ Repeat join request?</source>
|
||||
<target>Voit käyttää markdownia viestien muotoiluun:</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can view invitation link again in connection details." xml:space="preserve">
|
||||
<source>You can view invitation link again in connection details.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can't send messages!" xml:space="preserve">
|
||||
<source>You can't send messages!</source>
|
||||
<target>Et voi lähettää viestejä!</target>
|
||||
@@ -5792,13 +5955,6 @@ Voit peruuttaa tämän yhteyden ja poistaa kontaktin (ja yrittää myöhemmin uu
|
||||
<target>Kontaktisi voivat sallia viestien täydellisen poistamisen.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your contacts in SimpleX will see it. You can change it in Settings." xml:space="preserve">
|
||||
<source>Your contacts in SimpleX will see it.
|
||||
You can change it in Settings.</source>
|
||||
<target>Kontaktisi SimpleX:ssä näkevät sen.
|
||||
Voit muuttaa sitä Asetuksista.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your contacts will remain connected." xml:space="preserve">
|
||||
<source>Your contacts will remain connected.</source>
|
||||
<target>Kontaktisi pysyvät yhdistettyinä.</target>
|
||||
@@ -5947,6 +6103,14 @@ SimpleX-palvelimet eivät näe profiiliasi.</target>
|
||||
<source>blocked</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="blocked %@" xml:space="preserve">
|
||||
<source>blocked %@</source>
|
||||
<note>rcv group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="blocked by admin" xml:space="preserve">
|
||||
<source>blocked by admin</source>
|
||||
<note>blocked chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="bold" xml:space="preserve">
|
||||
<source>bold</source>
|
||||
<target>lihavoitu</target>
|
||||
@@ -6066,6 +6230,10 @@ SimpleX-palvelimet eivät näe profiiliasi.</target>
|
||||
<target>yhteys:%@</target>
|
||||
<note>connection information</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="contact %@ changed to %@" xml:space="preserve">
|
||||
<source>contact %1$@ changed to %2$@</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="contact has e2e encryption" xml:space="preserve">
|
||||
<source>contact has e2e encryption</source>
|
||||
<target>kontaktilla on e2e-salaus</target>
|
||||
@@ -6335,6 +6503,10 @@ SimpleX-palvelimet eivät näe profiiliasi.</target>
|
||||
<target>jäsen</target>
|
||||
<note>member role</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="member %@ changed to %@" xml:space="preserve">
|
||||
<source>member %1$@ changed to %2$@</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="member connected" xml:space="preserve">
|
||||
<source>connected</source>
|
||||
<target>yhdistetty</target>
|
||||
@@ -6457,6 +6629,14 @@ SimpleX-palvelimet eivät näe profiiliasi.</target>
|
||||
<target>%@ poistettu</target>
|
||||
<note>rcv group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="removed contact address" xml:space="preserve">
|
||||
<source>removed contact address</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="removed profile picture" xml:space="preserve">
|
||||
<source>removed profile picture</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="removed you" xml:space="preserve">
|
||||
<source>removed you</source>
|
||||
<target>poisti sinut</target>
|
||||
@@ -6486,6 +6666,14 @@ SimpleX-palvelimet eivät näe profiiliasi.</target>
|
||||
<source>send direct message</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="set new contact address" xml:space="preserve">
|
||||
<source>set new contact address</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="set new profile picture" xml:space="preserve">
|
||||
<source>set new profile picture</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="starting…" xml:space="preserve">
|
||||
<source>starting…</source>
|
||||
<target>alkaa…</target>
|
||||
@@ -6501,16 +6689,28 @@ SimpleX-palvelimet eivät näe profiiliasi.</target>
|
||||
<target>tämä kontakti</target>
|
||||
<note>notification title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="unblocked %@" xml:space="preserve">
|
||||
<source>unblocked %@</source>
|
||||
<note>rcv group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="unknown" xml:space="preserve">
|
||||
<source>unknown</source>
|
||||
<target>tuntematon</target>
|
||||
<note>connection info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="unknown status" xml:space="preserve">
|
||||
<source>unknown status</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="updated group profile" xml:space="preserve">
|
||||
<source>updated group profile</source>
|
||||
<target>päivitetty ryhmäprofiili</target>
|
||||
<note>rcv group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="updated profile" xml:space="preserve">
|
||||
<source>updated profile</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="v%@" xml:space="preserve">
|
||||
<source>v%@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -6580,6 +6780,10 @@ SimpleX-palvelimet eivät näe profiiliasi.</target>
|
||||
<target>olet tarkkailija</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="you blocked %@" xml:space="preserve">
|
||||
<source>you blocked %@</source>
|
||||
<note>snd group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="you changed address" xml:space="preserve">
|
||||
<source>you changed address</source>
|
||||
<target>muutit osoitetta</target>
|
||||
@@ -6620,6 +6824,10 @@ SimpleX-palvelimet eivät näe profiiliasi.</target>
|
||||
<target>jaoit kertalinkin incognito-tilassa</target>
|
||||
<note>chat list item description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="you unblocked %@" xml:space="preserve">
|
||||
<source>you unblocked %@</source>
|
||||
<note>snd group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="you: " xml:space="preserve">
|
||||
<source>you: </source>
|
||||
<target>sinä: </target>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -207,6 +207,10 @@
|
||||
<source>%lld messages blocked</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld messages blocked by admin" xml:space="preserve">
|
||||
<source>%lld messages blocked by admin</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld messages marked deleted" xml:space="preserve">
|
||||
<source>%lld messages marked deleted</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -297,14 +301,17 @@
|
||||
<target>)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Add contact**: to create a new invitation link, or connect via a link you received." xml:space="preserve">
|
||||
<source>**Add contact**: to create a new invitation link, or connect via a link you received.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Add new contact**: to create your one-time QR Code for your contact." xml:space="preserve">
|
||||
<source>**Add new contact**: to create your one-time QR Code or link for your contact.</source>
|
||||
<target>**เพิ่มผู้ติดต่อใหม่**: เพื่อสร้างคิวอาร์โค้ดแบบใช้ครั้งเดียวหรือลิงก์สำหรับผู้ติดต่อของคุณ</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Create link / QR code** for your contact to use." xml:space="preserve">
|
||||
<source>**Create link / QR code** for your contact to use.</source>
|
||||
<target>**สร้างลิงค์ / คิวอาร์โค้ด** เพื่อให้ผู้ติดต่อของคุณใช้</target>
|
||||
<trans-unit id="**Create group**: to create a new group." xml:space="preserve">
|
||||
<source>**Create group**: to create a new group.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." xml:space="preserve">
|
||||
@@ -317,11 +324,6 @@
|
||||
<target>**ส่วนตัวที่สุด**: ไม่ใช้เซิร์ฟเวอร์การแจ้งเตือนของ SimpleX Chat ตรวจสอบข้อความเป็นระยะในพื้นหลัง (ขึ้นอยู่กับความถี่ที่คุณใช้แอป)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Paste received link** or open it in the browser and tap **Open in mobile app**." xml:space="preserve">
|
||||
<source>**Paste received link** or open it in the browser and tap **Open in mobile app**.</source>
|
||||
<target>**แปะลิงก์ที่ได้รับ** หรือเปิดในเบราว์เซอร์แล้วแตะ **เปิดในแอปมือถือ**</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Please note**: you will NOT be able to recover or change passphrase if you lose it." xml:space="preserve">
|
||||
<source>**Please note**: you will NOT be able to recover or change passphrase if you lose it.</source>
|
||||
<target>**โปรดทราบ**: คุณจะไม่สามารถกู้คืนหรือเปลี่ยนรหัสผ่านได้หากคุณทำรหัสผ่านหาย</target>
|
||||
@@ -332,11 +334,6 @@
|
||||
<target>**แนะนำ**: โทเค็นอุปกรณ์และการแจ้งเตือนจะถูกส่งไปยังเซิร์ฟเวอร์การแจ้งเตือนของ SimpleX Chat แต่ไม่ใช่เนื้อหาข้อความ ขนาด หรือผู้ที่ส่ง</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Scan QR code**: to connect to your contact in person or via video call." xml:space="preserve">
|
||||
<source>**Scan QR code**: to connect to your contact in person or via video call.</source>
|
||||
<target>**สแกนคิวอาร์โค้ด**: เพื่อเชื่อมต่อกับผู้ติดต่อของคุณด้วยตนเองหรือผ่านการสนทนาทางวิดีโอ</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Warning**: Instant push notifications require passphrase saved in Keychain." xml:space="preserve">
|
||||
<source>**Warning**: Instant push notifications require passphrase saved in Keychain.</source>
|
||||
<target>**คำเตือน**: การแจ้งเตือนแบบพุชทันทีจำเป็นต้องบันทึกรหัสผ่านไว้ใน Keychain</target>
|
||||
@@ -431,11 +428,6 @@
|
||||
<target>1 สัปดาห์</target>
|
||||
<note>time interval</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="1-time link" xml:space="preserve">
|
||||
<source>1-time link</source>
|
||||
<target>ลิงก์สำหรับใช้ 1 ครั้ง</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="5 minutes" xml:space="preserve">
|
||||
<source>5 minutes</source>
|
||||
<target>5 นาที</target>
|
||||
@@ -549,6 +541,10 @@
|
||||
<target>เพิ่มที่อยู่ลงในโปรไฟล์ของคุณ เพื่อให้ผู้ติดต่อของคุณสามารถแชร์กับผู้อื่นได้ การอัปเดตโปรไฟล์จะถูกส่งไปยังผู้ติดต่อของคุณ</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add contact" xml:space="preserve">
|
||||
<source>Add contact</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add preset servers" xml:space="preserve">
|
||||
<source>Add preset servers</source>
|
||||
<target>เพิ่มเซิร์ฟเวอร์ที่ตั้งไว้ล่วงหน้า</target>
|
||||
@@ -619,6 +615,10 @@
|
||||
<target>สมาชิกในกลุ่มทุกคนจะยังคงเชื่อมต่ออยู่.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages will be deleted - this cannot be undone!" xml:space="preserve">
|
||||
<source>All messages will be deleted - this cannot be undone!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." xml:space="preserve">
|
||||
<source>All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you.</source>
|
||||
<target>ข้อความทั้งหมดจะถูกลบ - ไม่สามารถยกเลิกได้! ข้อความจะถูกลบสำหรับคุณเท่านั้น.</target>
|
||||
@@ -653,8 +653,8 @@
|
||||
<target>อนุญาตให้ข้อความที่หายไปเฉพาะในกรณีที่ผู้ติดต่อของคุณอนุญาตเท่านั้น.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow irreversible message deletion only if your contact allows it to you." xml:space="preserve">
|
||||
<source>Allow irreversible message deletion only if your contact allows it to you.</source>
|
||||
<trans-unit id="Allow irreversible message deletion only if your contact allows it to you. (24 hours)" xml:space="preserve">
|
||||
<source>Allow irreversible message deletion only if your contact allows it to you. (24 hours)</source>
|
||||
<target>อนุญาตให้ลบข้อความแบบถาวรเฉพาะในกรณีที่ผู้ติดต่อของคุณอนุญาตให้คุณเท่านั้น</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
@@ -678,8 +678,8 @@
|
||||
<target>อนุญาตให้ส่งข้อความที่จะหายไปหลังปิดแชท (disappearing message)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow to irreversibly delete sent messages." xml:space="preserve">
|
||||
<source>Allow to irreversibly delete sent messages.</source>
|
||||
<trans-unit id="Allow to irreversibly delete sent messages. (24 hours)" xml:space="preserve">
|
||||
<source>Allow to irreversibly delete sent messages. (24 hours)</source>
|
||||
<target>อนุญาตให้ลบข้อความที่ส่งไปแล้วอย่างถาวร</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
@@ -713,8 +713,8 @@
|
||||
<target>อนุญาตให้ผู้ติดต่อของคุณโทรหาคุณ</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow your contacts to irreversibly delete sent messages." xml:space="preserve">
|
||||
<source>Allow your contacts to irreversibly delete sent messages.</source>
|
||||
<trans-unit id="Allow your contacts to irreversibly delete sent messages. (24 hours)" xml:space="preserve">
|
||||
<source>Allow your contacts to irreversibly delete sent messages. (24 hours)</source>
|
||||
<target>อนุญาตให้ผู้ติดต่อของคุณลบข้อความที่ส่งแล้วอย่างถาวร</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
@@ -887,6 +887,10 @@
|
||||
<source>Block</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Block for all" xml:space="preserve">
|
||||
<source>Block for all</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Block group members" xml:space="preserve">
|
||||
<source>Block group members</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -895,17 +899,25 @@
|
||||
<source>Block member</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Block member for all?" xml:space="preserve">
|
||||
<source>Block member for all?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Block member?" xml:space="preserve">
|
||||
<source>Block member?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Blocked by admin" xml:space="preserve">
|
||||
<source>Blocked by admin</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
|
||||
<source>Both you and your contact can add message reactions.</source>
|
||||
<target>ทั้งคุณและผู้ติดต่อของคุณสามารถเพิ่มปฏิกิริยาของข้อความได้</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Both you and your contact can irreversibly delete sent messages." xml:space="preserve">
|
||||
<source>Both you and your contact can irreversibly delete sent messages.</source>
|
||||
<trans-unit id="Both you and your contact can irreversibly delete sent messages. (24 hours)" xml:space="preserve">
|
||||
<source>Both you and your contact can irreversibly delete sent messages. (24 hours)</source>
|
||||
<target>ทั้งคุณและผู้ติดต่อของคุณสามารถลบข้อความที่ส่งแล้วอย่างถาวรได้</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
@@ -943,9 +955,8 @@
|
||||
<target>โทร</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Can't delete user profile!" xml:space="preserve">
|
||||
<source>Can't delete user profile!</source>
|
||||
<target>ไม่สามารถลบโปรไฟล์ผู้ใช้ได้!</target>
|
||||
<trans-unit id="Camera not available" xml:space="preserve">
|
||||
<source>Camera not available</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Can't invite contact!" xml:space="preserve">
|
||||
@@ -1059,6 +1070,10 @@
|
||||
<target>การแชทหยุดทํางานแล้ว</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." xml:space="preserve">
|
||||
<source>Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat preferences" xml:space="preserve">
|
||||
<source>Chat preferences</source>
|
||||
<target>ค่ากําหนดในการแชท</target>
|
||||
@@ -1104,6 +1119,10 @@
|
||||
<target>ลบการสนทนา?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Clear private notes?" xml:space="preserve">
|
||||
<source>Clear private notes?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Clear verification" xml:space="preserve">
|
||||
<source>Clear verification</source>
|
||||
<target>ล้างการยืนยัน</target>
|
||||
@@ -1194,11 +1213,6 @@ This is your own one-time link!</source>
|
||||
<target>เชื่อมต่อผ่านลิงก์</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via link / QR code" xml:space="preserve">
|
||||
<source>Connect via link / QR code</source>
|
||||
<target>เชื่อมต่อผ่านลิงค์ / คิวอาร์โค้ด</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via one-time link" xml:space="preserve">
|
||||
<source>Connect via one-time link</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -1364,11 +1378,6 @@ This is your own one-time link!</source>
|
||||
<source>Create new profile in [desktop app](https://simplex.chat/downloads/). 💻</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create one-time invitation link" xml:space="preserve">
|
||||
<source>Create one-time invitation link</source>
|
||||
<target>สร้างลิงก์เชิญแบบใช้ครั้งเดียว</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create profile" xml:space="preserve">
|
||||
<source>Create profile</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -1388,11 +1397,23 @@ This is your own one-time link!</source>
|
||||
<target>สร้างโปรไฟล์ของคุณ</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Created at" xml:space="preserve">
|
||||
<source>Created at</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Created at: %@" xml:space="preserve">
|
||||
<source>Created at: %@</source>
|
||||
<note>copied message info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Created on %@" xml:space="preserve">
|
||||
<source>Created on %@</source>
|
||||
<target>สร้างเมื่อ %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Creating link…" xml:space="preserve">
|
||||
<source>Creating link…</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Current Passcode" xml:space="preserve">
|
||||
<source>Current Passcode</source>
|
||||
<target>รหัสผ่านปัจจุบัน</target>
|
||||
@@ -1862,6 +1883,10 @@ This cannot be undone!</source>
|
||||
<target>ทำในภายหลัง</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Do not send history to new members." xml:space="preserve">
|
||||
<source>Do not send history to new members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Don't create address" xml:space="preserve">
|
||||
<source>Don't create address</source>
|
||||
<target>อย่าสร้างที่อยู่</target>
|
||||
@@ -1932,6 +1957,10 @@ This cannot be undone!</source>
|
||||
<target>เปิดใช้งานการลบข้อความอัตโนมัติ?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enable camera access" xml:space="preserve">
|
||||
<source>Enable camera access</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enable for all" xml:space="preserve">
|
||||
<source>Enable for all</source>
|
||||
<target>เปิดใช้งานสําหรับทุกคน</target>
|
||||
@@ -1995,6 +2024,10 @@ This cannot be undone!</source>
|
||||
<target>ข้อความที่ encrypt หรือเหตุการณ์อื่น</target>
|
||||
<note>notification</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Encrypted message: app is stopped" xml:space="preserve">
|
||||
<source>Encrypted message: app is stopped</source>
|
||||
<note>notification</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Encrypted message: database error" xml:space="preserve">
|
||||
<source>Encrypted message: database error</source>
|
||||
<target>ข้อความที่ encrypt: ความผิดพลาดในฐานข้อมูล</target>
|
||||
@@ -2134,6 +2167,10 @@ This cannot be undone!</source>
|
||||
<source>Error creating member contact</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error creating message" xml:space="preserve">
|
||||
<source>Error creating message</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error creating profile!" xml:space="preserve">
|
||||
<source>Error creating profile!</source>
|
||||
<target>เกิดข้อผิดพลาดในการสร้างโปรไฟล์!</target>
|
||||
@@ -2218,6 +2255,10 @@ This cannot be undone!</source>
|
||||
<target>โหลดเซิร์ฟเวอร์ %@ ผิดพลาด</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error opening chat" xml:space="preserve">
|
||||
<source>Error opening chat</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error receiving file" xml:space="preserve">
|
||||
<source>Error receiving file</source>
|
||||
<target>เกิดข้อผิดพลาดในการรับไฟล์</target>
|
||||
@@ -2258,6 +2299,10 @@ This cannot be undone!</source>
|
||||
<target>เกิดข้อผิดพลาดในการบันทึกรหัสผ่านผู้ใช้</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error scanning code: %@" xml:space="preserve">
|
||||
<source>Error scanning code: %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error sending email" xml:space="preserve">
|
||||
<source>Error sending email</source>
|
||||
<target>เกิดข้อผิดพลาดในการส่งอีเมล</target>
|
||||
@@ -2581,8 +2626,8 @@ This cannot be undone!</source>
|
||||
<target>สมาชิกกลุ่มสามารถเพิ่มการแสดงปฏิกิริยาต่อข้อความได้</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group members can irreversibly delete sent messages." xml:space="preserve">
|
||||
<source>Group members can irreversibly delete sent messages.</source>
|
||||
<trans-unit id="Group members can irreversibly delete sent messages. (24 hours)" xml:space="preserve">
|
||||
<source>Group members can irreversibly delete sent messages. (24 hours)</source>
|
||||
<target>สมาชิกกลุ่มสามารถลบข้อความที่ส่งแล้วอย่างถาวร</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
@@ -2691,6 +2736,10 @@ This cannot be undone!</source>
|
||||
<target>ประวัติ</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="History is not sent to new members." xml:space="preserve">
|
||||
<source>History is not sent to new members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="How SimpleX works" xml:space="preserve">
|
||||
<source>How SimpleX works</source>
|
||||
<target>วิธีการ SimpleX ทํางานอย่างไร</target>
|
||||
@@ -2726,11 +2775,6 @@ This cannot be undone!</source>
|
||||
<target>หากคุณไม่สามารถพบกันในชีวิตจริงได้ ให้แสดงคิวอาร์โค้ดในวิดีโอคอล หรือแชร์ลิงก์</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link." xml:space="preserve">
|
||||
<source>If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link.</source>
|
||||
<target>หากคุณไม่สามารถพบปะด้วยตนเอง คุณสามารถ **สแกนคิวอาร์โค้ดผ่านการสนทนาทางวิดีโอ** หรือผู้ติดต่อของคุณสามารถแชร์ลิงก์เชิญได้</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="If you enter this passcode when opening the app, all app data will be irreversibly removed!" xml:space="preserve">
|
||||
<source>If you enter this passcode when opening the app, all app data will be irreversibly removed!</source>
|
||||
<target>หากคุณใส่รหัสผ่านนี้เมื่อเปิดแอป ข้อมูลแอปทั้งหมดจะถูกลบอย่างถาวร!</target>
|
||||
@@ -2786,6 +2830,10 @@ This cannot be undone!</source>
|
||||
<target>นำเข้าฐานข้อมูล</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Improved message delivery" xml:space="preserve">
|
||||
<source>Improved message delivery</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Improved privacy and security" xml:space="preserve">
|
||||
<source>Improved privacy and security</source>
|
||||
<target>ปรับปรุงความเป็นส่วนตัวและความปลอดภัยแล้ว</target>
|
||||
@@ -2885,15 +2933,31 @@ This cannot be undone!</source>
|
||||
<target>อินเตอร์เฟซ</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid QR code" xml:space="preserve">
|
||||
<source>Invalid QR code</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid connection link" xml:space="preserve">
|
||||
<source>Invalid connection link</source>
|
||||
<target>ลิงค์เชื่อมต่อไม่ถูกต้อง</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid display name!" xml:space="preserve">
|
||||
<source>Invalid display name!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid link" xml:space="preserve">
|
||||
<source>Invalid link</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid name!" xml:space="preserve">
|
||||
<source>Invalid name!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid response" xml:space="preserve">
|
||||
<source>Invalid response</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid server address!" xml:space="preserve">
|
||||
<source>Invalid server address!</source>
|
||||
<target>ที่อยู่เซิร์ฟเวอร์ไม่ถูกต้อง!</target>
|
||||
@@ -2984,6 +3048,10 @@ This cannot be undone!</source>
|
||||
<target>เข้าร่วมกลุ่ม</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Join group conversations" xml:space="preserve">
|
||||
<source>Join group conversations</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Join group?" xml:space="preserve">
|
||||
<source>Join group?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -3007,10 +3075,18 @@ This is your link for group %@!</source>
|
||||
<target>กำลังจะเข้าร่วมกลุ่ม</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Keep" xml:space="preserve">
|
||||
<source>Keep</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Keep the app open to use it from desktop" xml:space="preserve">
|
||||
<source>Keep the app open to use it from desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Keep unused invitation?" xml:space="preserve">
|
||||
<source>Keep unused invitation?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Keep your connections" xml:space="preserve">
|
||||
<source>Keep your connections</source>
|
||||
<target>รักษาการเชื่อมต่อของคุณ</target>
|
||||
@@ -3093,6 +3169,11 @@ This is your link for group %@!</source>
|
||||
<target>ข้อความสด</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Local" xml:space="preserve">
|
||||
<source>Local</source>
|
||||
<target>ในเครื่อง</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Local name" xml:space="preserve">
|
||||
<source>Local name</source>
|
||||
<target>ชื่อภายในเครื่องเท่านั้น</target>
|
||||
@@ -3331,6 +3412,10 @@ This is your link for group %@!</source>
|
||||
<target>รหัสผ่านใหม่</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="New chat" xml:space="preserve">
|
||||
<source>New chat</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="New contact request" xml:space="preserve">
|
||||
<source>New contact request</source>
|
||||
<target>คำขอติดต่อใหม่</target>
|
||||
@@ -3452,16 +3537,15 @@ This is your link for group %@!</source>
|
||||
- ปิดการใช้งานสมาชิก (บทบาท "ผู้สังเกตการณ์")</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="OK" xml:space="preserve">
|
||||
<source>OK</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Off" xml:space="preserve">
|
||||
<source>Off</source>
|
||||
<target>ปิด</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Off (Local)" xml:space="preserve">
|
||||
<source>Off (Local)</source>
|
||||
<target>ปิด (ในเครื่อง)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Ok" xml:space="preserve">
|
||||
<source>Ok</source>
|
||||
<target>ตกลง</target>
|
||||
@@ -3522,8 +3606,8 @@ This is your link for group %@!</source>
|
||||
<target>มีเพียงคุณเท่านั้นที่สามารถแสดงปฏิกิริยาต่อข้อความได้</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Only you can irreversibly delete messages (your contact can mark them for deletion)." xml:space="preserve">
|
||||
<source>Only you can irreversibly delete messages (your contact can mark them for deletion).</source>
|
||||
<trans-unit id="Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)" xml:space="preserve">
|
||||
<source>Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)</source>
|
||||
<target>มีเพียงคุณเท่านั้นที่สามารถลบข้อความแบบย้อนกลับไม่ได้ (ผู้ติดต่อของคุณสามารถทำเครื่องหมายเพื่อลบได้)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
@@ -3547,8 +3631,8 @@ This is your link for group %@!</source>
|
||||
<target>เฉพาะผู้ติดต่อของคุณเท่านั้นที่สามารถเพิ่มการโต้ตอบข้อความได้</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Only your contact can irreversibly delete messages (you can mark them for deletion)." xml:space="preserve">
|
||||
<source>Only your contact can irreversibly delete messages (you can mark them for deletion).</source>
|
||||
<trans-unit id="Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours)" xml:space="preserve">
|
||||
<source>Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours)</source>
|
||||
<target>เฉพาะผู้ติดต่อของคุณเท่านั้นที่สามารถลบข้อความแบบย้อนกลับไม่ได้ (คุณสามารถทำเครื่องหมายเพื่อลบได้)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
@@ -3600,9 +3684,16 @@ This is your link for group %@!</source>
|
||||
<target>โปรโตคอลและโค้ดโอเพ่นซอร์ส – ใคร ๆ ก็สามารถเปิดใช้เซิร์ฟเวอร์ได้</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Opening database…" xml:space="preserve">
|
||||
<source>Opening database…</source>
|
||||
<target>กำลังเปิดฐานข้อมูล…</target>
|
||||
<trans-unit id="Opening app…" xml:space="preserve">
|
||||
<source>Opening app…</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Or scan QR code" xml:space="preserve">
|
||||
<source>Or scan QR code</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Or show this code" xml:space="preserve">
|
||||
<source>Or show this code</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="PING count" xml:space="preserve">
|
||||
@@ -3645,10 +3736,9 @@ This is your link for group %@!</source>
|
||||
<target>รหัสผ่านที่จะแสดง</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste" xml:space="preserve">
|
||||
<source>Paste</source>
|
||||
<target>แปะ</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<trans-unit id="Past member %@" xml:space="preserve">
|
||||
<source>Past member %@</source>
|
||||
<note>past/unknown group member</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste desktop address" xml:space="preserve">
|
||||
<source>Paste desktop address</source>
|
||||
@@ -3659,14 +3749,13 @@ This is your link for group %@!</source>
|
||||
<target>แปะภาพ</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste received link" xml:space="preserve">
|
||||
<source>Paste received link</source>
|
||||
<target>แปะลิงก์ที่ได้รับ</target>
|
||||
<trans-unit id="Paste link to connect!" xml:space="preserve">
|
||||
<source>Paste link to connect!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste the link you received to connect with your contact." xml:space="preserve">
|
||||
<source>Paste the link you received to connect with your contact.</source>
|
||||
<note>placeholder</note>
|
||||
<trans-unit id="Paste the link you received" xml:space="preserve">
|
||||
<source>Paste the link you received</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="People can connect to you only via the links you share." xml:space="preserve">
|
||||
<source>People can connect to you only via the links you share.</source>
|
||||
@@ -3703,6 +3792,11 @@ This is your link for group %@!</source>
|
||||
<target>โปรดตรวจสอบความต้องการของคุณและการตั้งค่าผู้ติดต่อ</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please contact developers. Error: %@" xml:space="preserve">
|
||||
<source>Please contact developers.
|
||||
Error: %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please contact group admin." xml:space="preserve">
|
||||
<source>Please contact group admin.</source>
|
||||
<target>โปรดติดต่อผู้ดูแลกลุ่ม</target>
|
||||
@@ -3788,6 +3882,10 @@ This is your link for group %@!</source>
|
||||
<target>ชื่อไฟล์ส่วนตัว</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Private notes" xml:space="preserve">
|
||||
<source>Private notes</source>
|
||||
<note>name of notes to self</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Profile and server connections" xml:space="preserve">
|
||||
<source>Profile and server connections</source>
|
||||
<target>การเชื่อมต่อโปรไฟล์และเซิร์ฟเวอร์</target>
|
||||
@@ -3906,6 +4004,10 @@ This is your link for group %@!</source>
|
||||
<target>อ่านเพิ่มเติมใน[คู่มือผู้ใช้](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." xml:space="preserve">
|
||||
<source>Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode).</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." xml:space="preserve">
|
||||
<source>Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends).</source>
|
||||
<target>อ่านเพิ่มเติมใน[คู่มือผู้ใช้](https://simplex.chat/docs/guide/readme.html#connect-to-friends)</target>
|
||||
@@ -3960,6 +4062,10 @@ This is your link for group %@!</source>
|
||||
<target>กำลังรับผ่าน</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Recent history and improved [directory bot](simplex:/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)." xml:space="preserve">
|
||||
<source>Recent history and improved [directory bot](simplex:/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).</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Recipients see updates as you type them." xml:space="preserve">
|
||||
<source>Recipients see updates as you type them.</source>
|
||||
<target>ผู้รับจะเห็นการอัปเดตเมื่อคุณพิมพ์</target>
|
||||
@@ -4112,6 +4218,10 @@ This is your link for group %@!</source>
|
||||
<target>กู้คืนข้อผิดพลาดของฐานข้อมูล</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Retry" xml:space="preserve">
|
||||
<source>Retry</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reveal" xml:space="preserve">
|
||||
<source>Reveal</source>
|
||||
<target>เปิดเผย</target>
|
||||
@@ -4237,6 +4347,10 @@ This is your link for group %@!</source>
|
||||
<target>เซิร์ฟเวอร์ WebRTC ICE ที่บันทึกไว้จะถูกลบออก</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Saved message" xml:space="preserve">
|
||||
<source>Saved message</source>
|
||||
<note>message info title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Scan QR code" xml:space="preserve">
|
||||
<source>Scan QR code</source>
|
||||
<target>สแกนคิวอาร์โค้ด</target>
|
||||
@@ -4266,6 +4380,14 @@ This is your link for group %@!</source>
|
||||
<target>ค้นหา</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search bar accepts invitation links." xml:space="preserve">
|
||||
<source>Search bar accepts invitation 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="Secure queue" xml:space="preserve">
|
||||
<source>Secure queue</source>
|
||||
<target>คิวที่ปลอดภัย</target>
|
||||
@@ -4370,6 +4492,10 @@ This is your link for group %@!</source>
|
||||
<target>ส่งจากแกลเลอรีหรือแป้นพิมพ์แบบกำหนดเอง</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Send up to 100 last messages to new members." xml:space="preserve">
|
||||
<source>Send up to 100 last messages to new members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sender cancelled file transfer." xml:space="preserve">
|
||||
<source>Sender cancelled file transfer.</source>
|
||||
<target>ผู้ส่งยกเลิกการโอนไฟล์</target>
|
||||
@@ -4537,9 +4663,8 @@ This is your link for group %@!</source>
|
||||
<target>แชร์ลิงก์</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share one-time invitation link" xml:space="preserve">
|
||||
<source>Share one-time invitation link</source>
|
||||
<target>แชร์ลิงก์เชิญแบบใช้ครั้งเดียว</target>
|
||||
<trans-unit id="Share this 1-time invite link" xml:space="preserve">
|
||||
<source>Share this 1-time invite link</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share with contacts" xml:space="preserve">
|
||||
@@ -4659,16 +4784,15 @@ This is your link for group %@!</source>
|
||||
<target>ใครบางคน</target>
|
||||
<note>notification title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Start a new chat" xml:space="preserve">
|
||||
<source>Start a new chat</source>
|
||||
<target>เริ่มแชทใหม่</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Start chat" xml:space="preserve">
|
||||
<source>Start chat</source>
|
||||
<target>เริ่มแชท</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Start chat?" xml:space="preserve">
|
||||
<source>Start chat?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Start migration" xml:space="preserve">
|
||||
<source>Start migration</source>
|
||||
<target>เริ่มการย้ายข้อมูล</target>
|
||||
@@ -4793,6 +4917,14 @@ This is your link for group %@!</source>
|
||||
<target>แตะเพื่อเข้าร่วมโหมดไม่ระบุตัวตน</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to paste link" xml:space="preserve">
|
||||
<source>Tap to paste link</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to scan" xml:space="preserve">
|
||||
<source>Tap to scan</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to start a new chat" xml:space="preserve">
|
||||
<source>Tap to start a new chat</source>
|
||||
<target>แตะเพื่อเริ่มแชทใหม่</target>
|
||||
@@ -4856,6 +4988,10 @@ It can happen because of some bug or when the connection is compromised.</source
|
||||
<target>ความพยายามในการเปลี่ยนรหัสผ่านของฐานข้อมูลไม่เสร็จสมบูรณ์</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The code you scanned is not a SimpleX link QR code." xml:space="preserve">
|
||||
<source>The code you scanned is not a SimpleX link QR code.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The connection you accepted will be cancelled!" xml:space="preserve">
|
||||
<source>The connection you accepted will be cancelled!</source>
|
||||
<target>การเชื่อมต่อที่คุณยอมรับจะถูกยกเลิก!</target>
|
||||
@@ -4921,21 +5057,15 @@ It can happen because of some bug or when the connection is compromised.</source
|
||||
<target>เซิร์ฟเวอร์สำหรับการเชื่อมต่อใหม่ของโปรไฟล์การแชทปัจจุบันของคุณ **%@**</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The text you pasted is not a SimpleX link." xml:space="preserve">
|
||||
<source>The text you pasted is not a SimpleX link.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Theme" xml:space="preserve">
|
||||
<source>Theme</source>
|
||||
<target>ธีม</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="There should be at least one user profile." xml:space="preserve">
|
||||
<source>There should be at least one user profile.</source>
|
||||
<target>ควรมีโปรไฟล์ผู้ใช้อย่างน้อยหนึ่งโปรไฟล์</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="There should be at least one visible user profile." xml:space="preserve">
|
||||
<source>There should be at least one visible user profile.</source>
|
||||
<target>ควรมีอย่างน้อยหนึ่งโปรไฟล์ผู้ใช้ที่มองเห็นได้</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="These settings are for your current profile **%@**." xml:space="preserve">
|
||||
<source>These settings are for your current profile **%@**.</source>
|
||||
<target>การตั้งค่าเหล่านี้ใช้สำหรับโปรไฟล์ปัจจุบันของคุณ **%@**</target>
|
||||
@@ -4964,6 +5094,10 @@ It can happen because of some bug or when the connection is compromised.</source
|
||||
<source>This device name</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This display name is invalid. Please choose another name." xml:space="preserve">
|
||||
<source>This display name is invalid. Please choose another name.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This group has over %lld members, delivery receipts are not sent." xml:space="preserve">
|
||||
<source>This group has over %lld members, delivery receipts are not sent.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -5061,16 +5195,15 @@ You will be prompted to complete authentication before this feature is enabled.<
|
||||
<target>พยายามเชื่อมต่อกับเซิร์ฟเวอร์ที่ใช้รับข้อความจากผู้ติดต่อนี้</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Turkish interface" xml:space="preserve">
|
||||
<source>Turkish interface</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Turn off" xml:space="preserve">
|
||||
<source>Turn off</source>
|
||||
<target>ปิด</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Turn off notifications?" xml:space="preserve">
|
||||
<source>Turn off notifications?</source>
|
||||
<target>ปิดการแจ้งเตือนไหม?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Turn on" xml:space="preserve">
|
||||
<source>Turn on</source>
|
||||
<target>เปิด</target>
|
||||
@@ -5085,10 +5218,18 @@ You will be prompted to complete authentication before this feature is enabled.<
|
||||
<source>Unblock</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unblock for all" xml:space="preserve">
|
||||
<source>Unblock for all</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unblock member" xml:space="preserve">
|
||||
<source>Unblock member</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unblock member for all?" xml:space="preserve">
|
||||
<source>Unblock member for all?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unblock member?" xml:space="preserve">
|
||||
<source>Unblock member?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -5183,6 +5324,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="Up to 100 last messages are sent to new members." xml:space="preserve">
|
||||
<source>Up to 100 last messages are sent to new members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Update" xml:space="preserve">
|
||||
<source>Update</source>
|
||||
<target>อัปเดต</target>
|
||||
@@ -5265,6 +5410,10 @@ To connect, please ask your contact to create another connection link and check
|
||||
<source>Use new incognito profile</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Use only local notifications?" xml:space="preserve">
|
||||
<source>Use only local notifications?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Use server" xml:space="preserve">
|
||||
<source>Use server</source>
|
||||
<target>ใช้เซิร์ฟเวอร์</target>
|
||||
@@ -5341,6 +5490,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="Visible history" xml:space="preserve">
|
||||
<source>Visible history</source>
|
||||
<note>chat feature</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Voice messages" xml:space="preserve">
|
||||
<source>Voice messages</source>
|
||||
<target>ข้อความเสียง</target>
|
||||
@@ -5425,11 +5578,19 @@ 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="With encrypted files and media." xml:space="preserve">
|
||||
<source>With encrypted files and media.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="With optional welcome message." xml:space="preserve">
|
||||
<source>With optional welcome message.</source>
|
||||
<target>พร้อมข้อความต้อนรับที่ไม่บังคับ</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="With reduced battery usage." xml:space="preserve">
|
||||
<source>With reduced battery usage.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Wrong database passphrase" xml:space="preserve">
|
||||
<source>Wrong database passphrase</source>
|
||||
<target>รหัสผ่านฐานข้อมูลไม่ถูกต้อง</target>
|
||||
@@ -5514,11 +5675,6 @@ Repeat join request?</source>
|
||||
<target>คุณสามารถรับสายจากหน้าจอล็อกโดยไม่ต้องมีการตรวจสอบสิทธิ์อุปกรณ์และแอป</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button." xml:space="preserve">
|
||||
<source>You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button.</source>
|
||||
<target>คุณสามารถเชื่อมต่อได้โดยคลิกที่ลิงค์ หากเปิดในเบราว์เซอร์ ให้คลิกปุ่ม **เปิดในแอปมือถือ**</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can create it later" xml:space="preserve">
|
||||
<source>You can create it later</source>
|
||||
<target>คุณสามารถสร้างได้ในภายหลัง</target>
|
||||
@@ -5539,6 +5695,10 @@ Repeat join request?</source>
|
||||
<target>คุณสามารถซ่อนหรือปิดเสียงโปรไฟล์ผู้ใช้ - ปัดไปทางขวา</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can make it visible to your SimpleX contacts via Settings." xml:space="preserve">
|
||||
<source>You can make it visible to your SimpleX contacts via Settings.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can now send messages to %@" xml:space="preserve">
|
||||
<source>You can now send messages to %@</source>
|
||||
<target>ตอนนี้คุณสามารถส่งข้อความถึง %@</target>
|
||||
@@ -5579,6 +5739,10 @@ Repeat join request?</source>
|
||||
<target>คุณสามารถใช้มาร์กดาวน์เพื่อจัดรูปแบบข้อความ:</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can view invitation link again in connection details." xml:space="preserve">
|
||||
<source>You can view invitation link again in connection details.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can't send messages!" xml:space="preserve">
|
||||
<source>You can't send messages!</source>
|
||||
<target>คุณไม่สามารถส่งข้อความได้!</target>
|
||||
@@ -5762,13 +5926,6 @@ You can cancel this connection and remove the contact (and try later with a new
|
||||
<target>ผู้ติดต่อของคุณสามารถอนุญาตให้ลบข้อความทั้งหมดได้</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your contacts in SimpleX will see it. You can change it in Settings." xml:space="preserve">
|
||||
<source>Your contacts in SimpleX will see it.
|
||||
You can change it in Settings.</source>
|
||||
<target>ผู้ติดต่อของคุณใน SimpleX จะเห็น
|
||||
คุณสามารถเปลี่ยนได้ในการตั้งค่า</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your contacts will remain connected." xml:space="preserve">
|
||||
<source>Your contacts will remain connected.</source>
|
||||
<target>ผู้ติดต่อของคุณจะยังคงเชื่อมต่ออยู่</target>
|
||||
@@ -5916,6 +6073,14 @@ SimpleX servers cannot see your profile.</source>
|
||||
<source>blocked</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="blocked %@" xml:space="preserve">
|
||||
<source>blocked %@</source>
|
||||
<note>rcv group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="blocked by admin" xml:space="preserve">
|
||||
<source>blocked by admin</source>
|
||||
<note>blocked chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="bold" xml:space="preserve">
|
||||
<source>bold</source>
|
||||
<target>ตัวหนา</target>
|
||||
@@ -6035,6 +6200,10 @@ SimpleX servers cannot see your profile.</source>
|
||||
<target>การเชื่อมต่อ:%@</target>
|
||||
<note>connection information</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="contact %@ changed to %@" xml:space="preserve">
|
||||
<source>contact %1$@ changed to %2$@</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="contact has e2e encryption" xml:space="preserve">
|
||||
<source>contact has e2e encryption</source>
|
||||
<target>ผู้ติดต่อมีการ encrypt จากต้นจนจบ</target>
|
||||
@@ -6302,6 +6471,10 @@ SimpleX servers cannot see your profile.</source>
|
||||
<target>สมาชิก</target>
|
||||
<note>member role</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="member %@ changed to %@" xml:space="preserve">
|
||||
<source>member %1$@ changed to %2$@</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="member connected" xml:space="preserve">
|
||||
<source>connected</source>
|
||||
<target>เชื่อมต่อสำเร็จ</target>
|
||||
@@ -6424,6 +6597,14 @@ SimpleX servers cannot see your profile.</source>
|
||||
<target>ถูกลบแล้ว %@</target>
|
||||
<note>rcv group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="removed contact address" xml:space="preserve">
|
||||
<source>removed contact address</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="removed profile picture" xml:space="preserve">
|
||||
<source>removed profile picture</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="removed you" xml:space="preserve">
|
||||
<source>removed you</source>
|
||||
<target>ลบคุณออกแล้ว</target>
|
||||
@@ -6453,6 +6634,14 @@ SimpleX servers cannot see your profile.</source>
|
||||
<source>send direct message</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="set new contact address" xml:space="preserve">
|
||||
<source>set new contact address</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="set new profile picture" xml:space="preserve">
|
||||
<source>set new profile picture</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="starting…" xml:space="preserve">
|
||||
<source>starting…</source>
|
||||
<target>กำลังเริ่มต้น…</target>
|
||||
@@ -6468,16 +6657,28 @@ SimpleX servers cannot see your profile.</source>
|
||||
<target>ผู้ติดต่อนี้</target>
|
||||
<note>notification title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="unblocked %@" xml:space="preserve">
|
||||
<source>unblocked %@</source>
|
||||
<note>rcv group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="unknown" xml:space="preserve">
|
||||
<source>unknown</source>
|
||||
<target>ไม่ทราบ</target>
|
||||
<note>connection info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="unknown status" xml:space="preserve">
|
||||
<source>unknown status</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="updated group profile" xml:space="preserve">
|
||||
<source>updated group profile</source>
|
||||
<target>อัปเดตโปรไฟล์กลุ่มแล้ว</target>
|
||||
<note>rcv group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="updated profile" xml:space="preserve">
|
||||
<source>updated profile</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="v%@" xml:space="preserve">
|
||||
<source>v%@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -6547,6 +6748,10 @@ SimpleX servers cannot see your profile.</source>
|
||||
<target>คุณเป็นผู้สังเกตการณ์</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="you blocked %@" xml:space="preserve">
|
||||
<source>you blocked %@</source>
|
||||
<note>snd group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="you changed address" xml:space="preserve">
|
||||
<source>you changed address</source>
|
||||
<target>คุณเปลี่ยนที่อยู่แล้ว</target>
|
||||
@@ -6587,6 +6792,10 @@ SimpleX servers cannot see your profile.</source>
|
||||
<target>คุณแชร์ลิงก์แบบใช้ครั้งเดียวโดยไม่ระบุตัวตนแล้ว</target>
|
||||
<note>chat list item description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="you unblocked %@" xml:space="preserve">
|
||||
<source>you unblocked %@</source>
|
||||
<note>snd group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="you: " xml:space="preserve">
|
||||
<source>you: </source>
|
||||
<target>คุณ: </target>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"locale" : "tr"
|
||||
}
|
||||
],
|
||||
"properties" : {
|
||||
"localizable" : true
|
||||
},
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"red" : "0.000",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "1.000",
|
||||
"green" : "0.533"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"properties" : {
|
||||
"localizable" : true
|
||||
},
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/* Bundle display name */
|
||||
"CFBundleDisplayName" = "SimpleX NSE";
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "SimpleX NSE";
|
||||
/* Copyright (human-readable) */
|
||||
"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. All rights reserved.";
|
||||
@@ -0,0 +1,30 @@
|
||||
/* No comment provided by engineer. */
|
||||
"_italic_" = "\\_italic_";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"*bold*" = "\\*bold*";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"`a + b`" = "\\`a + b`";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"~strike~" = "\\~strike~";
|
||||
|
||||
/* call status */
|
||||
"connecting call" = "connecting call…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connecting server…" = "Connecting to server…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connecting server… (error: %@)" = "Connecting to server… (error: %@)";
|
||||
|
||||
/* rcv group event chat item */
|
||||
"member connected" = "connected";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No group!" = "Group not found!";
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "SimpleX";
|
||||
/* Privacy - Camera Usage Description */
|
||||
"NSCameraUsageDescription" = "SimpleX needs camera access to scan QR codes to connect to other users and for video calls.";
|
||||
/* Privacy - Face ID Usage Description */
|
||||
"NSFaceIDUsageDescription" = "SimpleX uses Face ID for local authentication";
|
||||
/* Privacy - Local Network Usage Description */
|
||||
"NSLocalNetworkUsageDescription" = "SimpleX uses local network access to allow using user chat profile via desktop app on the same network.";
|
||||
/* Privacy - Microphone Usage Description */
|
||||
"NSMicrophoneUsageDescription" = "SimpleX needs microphone access for audio and video calls, and to record voice messages.";
|
||||
/* Privacy - Photo Library Additions Usage Description */
|
||||
"NSPhotoLibraryAddUsageDescription" = "SimpleX needs access to Photo Library for saving captured and received media";
|
||||
12
apps/ios/SimpleX Localizations/tr.xcloc/contents.json
Normal file
12
apps/ios/SimpleX Localizations/tr.xcloc/contents.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"developmentRegion" : "en",
|
||||
"project" : "SimpleX.xcodeproj",
|
||||
"targetLocale" : "tr",
|
||||
"toolInfo" : {
|
||||
"toolBuildNumber" : "15A240d",
|
||||
"toolID" : "com.apple.dt.xcode",
|
||||
"toolName" : "Xcode",
|
||||
"toolVersion" : "15.0"
|
||||
},
|
||||
"version" : "1.0"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -212,6 +212,10 @@
|
||||
<source>%lld messages blocked</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld messages blocked by admin" xml:space="preserve">
|
||||
<source>%lld messages blocked by admin</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld messages marked deleted" xml:space="preserve">
|
||||
<source>%lld messages marked deleted</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -303,14 +307,17 @@
|
||||
<target>)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Add contact**: to create a new invitation link, or connect via a link you received." xml:space="preserve">
|
||||
<source>**Add contact**: to create a new invitation link, or connect via a link you received.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Add new contact**: to create your one-time QR Code for your contact." xml:space="preserve">
|
||||
<source>**Add new contact**: to create your one-time QR Code or link for your contact.</source>
|
||||
<target>**添加新联系人**:为您的联系人创建一次性二维码或者链接。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Create link / QR code** for your contact to use." xml:space="preserve">
|
||||
<source>**Create link / QR code** for your contact to use.</source>
|
||||
<target>**创建链接 / 二维码** 给您的联系人使用。</target>
|
||||
<trans-unit id="**Create group**: to create a new group." xml:space="preserve">
|
||||
<source>**Create group**: to create a new group.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." xml:space="preserve">
|
||||
@@ -323,11 +330,6 @@
|
||||
<target>**最私密**:不使用 SimpleX Chat 通知服务器,在后台定期检查消息(取决于您多经常使用应用程序)。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Paste received link** or open it in the browser and tap **Open in mobile app**." xml:space="preserve">
|
||||
<source>**Paste received link** or open it in the browser and tap **Open in mobile app**.</source>
|
||||
<target>**粘贴收到的链接**或者在浏览器里打开并且点击**在移动应用程序里打开**。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Please note**: you will NOT be able to recover or change passphrase if you lose it." xml:space="preserve">
|
||||
<source>**Please note**: you will NOT be able to recover or change passphrase if you lose it.</source>
|
||||
<target>**请注意**:如果您丢失密码,您将无法恢复或者更改密码。</target>
|
||||
@@ -338,11 +340,6 @@
|
||||
<target>**推荐**:设备令牌和通知会发送至 SimpleX Chat 通知服务器,但是消息内容、大小或者发送人不会。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Scan QR code**: to connect to your contact in person or via video call." xml:space="preserve">
|
||||
<source>**Scan QR code**: to connect to your contact in person or via video call.</source>
|
||||
<target>**扫描二维码**:见面或者通过视频通话来连接您的联系人。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="**Warning**: Instant push notifications require passphrase saved in Keychain." xml:space="preserve">
|
||||
<source>**Warning**: Instant push notifications require passphrase saved in Keychain.</source>
|
||||
<target>**警告**:及时推送通知需要保存在钥匙串的密码。</target>
|
||||
@@ -440,11 +437,6 @@
|
||||
<target>1周</target>
|
||||
<note>time interval</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="1-time link" xml:space="preserve">
|
||||
<source>1-time link</source>
|
||||
<target>一次性链接</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="5 minutes" xml:space="preserve">
|
||||
<source>5 minutes</source>
|
||||
<target>5分钟</target>
|
||||
@@ -560,6 +552,10 @@
|
||||
<target>将地址添加到您的个人资料,以便您的联系人可以与其他人共享。个人资料更新将发送给您的联系人。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add contact" xml:space="preserve">
|
||||
<source>Add contact</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add preset servers" xml:space="preserve">
|
||||
<source>Add preset servers</source>
|
||||
<target>添加预设服务器</target>
|
||||
@@ -630,6 +626,10 @@
|
||||
<target>所有群组成员将保持连接。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages will be deleted - this cannot be undone!" xml:space="preserve">
|
||||
<source>All messages will be deleted - this cannot be undone!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." xml:space="preserve">
|
||||
<source>All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you.</source>
|
||||
<target>所有聊天记录和消息将被删除——这一行为无法撤销!只有您的消息会被删除。</target>
|
||||
@@ -664,8 +664,8 @@
|
||||
<target>仅当您的联系人允许时才允许限时消息。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow irreversible message deletion only if your contact allows it to you." xml:space="preserve">
|
||||
<source>Allow irreversible message deletion only if your contact allows it to you.</source>
|
||||
<trans-unit id="Allow irreversible message deletion only if your contact allows it to you. (24 hours)" xml:space="preserve">
|
||||
<source>Allow irreversible message deletion only if your contact allows it to you. (24 hours)</source>
|
||||
<target>仅有您的联系人许可后才允许不可撤回消息移除。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
@@ -689,8 +689,8 @@
|
||||
<target>允许发送限时消息。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow to irreversibly delete sent messages." xml:space="preserve">
|
||||
<source>Allow to irreversibly delete sent messages.</source>
|
||||
<trans-unit id="Allow to irreversibly delete sent messages. (24 hours)" xml:space="preserve">
|
||||
<source>Allow to irreversibly delete sent messages. (24 hours)</source>
|
||||
<target>允许不可撤回地删除已发送消息。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
@@ -724,8 +724,8 @@
|
||||
<target>允许您的联系人给您打电话。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Allow your contacts to irreversibly delete sent messages." xml:space="preserve">
|
||||
<source>Allow your contacts to irreversibly delete sent messages.</source>
|
||||
<trans-unit id="Allow your contacts to irreversibly delete sent messages. (24 hours)" xml:space="preserve">
|
||||
<source>Allow your contacts to irreversibly delete sent messages. (24 hours)</source>
|
||||
<target>允许您的联系人不可撤回地删除已发送消息。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
@@ -899,6 +899,10 @@
|
||||
<source>Block</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Block for all" xml:space="preserve">
|
||||
<source>Block for all</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Block group members" xml:space="preserve">
|
||||
<source>Block group members</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -907,17 +911,25 @@
|
||||
<source>Block member</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Block member for all?" xml:space="preserve">
|
||||
<source>Block member for all?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Block member?" xml:space="preserve">
|
||||
<source>Block member?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Blocked by admin" xml:space="preserve">
|
||||
<source>Blocked by admin</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Both you and your contact can add message reactions." xml:space="preserve">
|
||||
<source>Both you and your contact can add message reactions.</source>
|
||||
<target>您和您的联系人都可以添加消息回应。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Both you and your contact can irreversibly delete sent messages." xml:space="preserve">
|
||||
<source>Both you and your contact can irreversibly delete sent messages.</source>
|
||||
<trans-unit id="Both you and your contact can irreversibly delete sent messages. (24 hours)" xml:space="preserve">
|
||||
<source>Both you and your contact can irreversibly delete sent messages. (24 hours)</source>
|
||||
<target>您和您的联系人都可以不可逆转地删除已发送的消息。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
@@ -956,9 +968,8 @@
|
||||
<target>通话</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Can't delete user profile!" xml:space="preserve">
|
||||
<source>Can't delete user profile!</source>
|
||||
<target>无法删除用户个人资料!</target>
|
||||
<trans-unit id="Camera not available" xml:space="preserve">
|
||||
<source>Camera not available</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Can't invite contact!" xml:space="preserve">
|
||||
@@ -1072,6 +1083,10 @@
|
||||
<target>聊天已停止</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." xml:space="preserve">
|
||||
<source>Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat preferences" xml:space="preserve">
|
||||
<source>Chat preferences</source>
|
||||
<target>聊天偏好设置</target>
|
||||
@@ -1117,6 +1132,10 @@
|
||||
<target>清除对话吗?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Clear private notes?" xml:space="preserve">
|
||||
<source>Clear private notes?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Clear verification" xml:space="preserve">
|
||||
<source>Clear verification</source>
|
||||
<target>清除验证</target>
|
||||
@@ -1208,11 +1227,6 @@ This is your own one-time link!</source>
|
||||
<target>通过链接连接</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via link / QR code" xml:space="preserve">
|
||||
<source>Connect via link / QR code</source>
|
||||
<target>通过群组链接/二维码连接</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via one-time link" xml:space="preserve">
|
||||
<source>Connect via one-time link</source>
|
||||
<target>通过一次性链接连接</target>
|
||||
@@ -1380,11 +1394,6 @@ This is your own one-time link!</source>
|
||||
<target>在[桌面应用程序](https://simplex.chat/downloads/)中创建新的个人资料。 💻</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create one-time invitation link" xml:space="preserve">
|
||||
<source>Create one-time invitation link</source>
|
||||
<target>创建一次性邀请链接</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create profile" xml:space="preserve">
|
||||
<source>Create profile</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -1404,11 +1413,23 @@ This is your own one-time link!</source>
|
||||
<target>创建您的资料</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Created at" xml:space="preserve">
|
||||
<source>Created at</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Created at: %@" xml:space="preserve">
|
||||
<source>Created at: %@</source>
|
||||
<note>copied message info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Created on %@" xml:space="preserve">
|
||||
<source>Created on %@</source>
|
||||
<target>创建于 %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Creating link…" xml:space="preserve">
|
||||
<source>Creating link…</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Current Passcode" xml:space="preserve">
|
||||
<source>Current Passcode</source>
|
||||
<target>当前密码</target>
|
||||
@@ -1880,6 +1901,10 @@ This cannot be undone!</source>
|
||||
<target>稍后再做</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Do not send history to new members." xml:space="preserve">
|
||||
<source>Do not send history to new members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Don't create address" xml:space="preserve">
|
||||
<source>Don't create address</source>
|
||||
<target>不创建地址</target>
|
||||
@@ -1950,6 +1975,10 @@ This cannot be undone!</source>
|
||||
<target>启用自动删除消息?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enable camera access" xml:space="preserve">
|
||||
<source>Enable camera access</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enable for all" xml:space="preserve">
|
||||
<source>Enable for all</source>
|
||||
<target>全部启用</target>
|
||||
@@ -2015,6 +2044,10 @@ This cannot be undone!</source>
|
||||
<target>加密消息或其他事件</target>
|
||||
<note>notification</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Encrypted message: app is stopped" xml:space="preserve">
|
||||
<source>Encrypted message: app is stopped</source>
|
||||
<note>notification</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Encrypted message: database error" xml:space="preserve">
|
||||
<source>Encrypted message: database error</source>
|
||||
<target>加密消息:数据库错误</target>
|
||||
@@ -2155,6 +2188,10 @@ This cannot be undone!</source>
|
||||
<target>创建成员联系人时出错</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error creating message" xml:space="preserve">
|
||||
<source>Error creating message</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error creating profile!" xml:space="preserve">
|
||||
<source>Error creating profile!</source>
|
||||
<target>创建资料错误!</target>
|
||||
@@ -2240,6 +2277,10 @@ This cannot be undone!</source>
|
||||
<target>加载 %@ 服务器错误</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error opening chat" xml:space="preserve">
|
||||
<source>Error opening chat</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error receiving file" xml:space="preserve">
|
||||
<source>Error receiving file</source>
|
||||
<target>接收文件错误</target>
|
||||
@@ -2280,6 +2321,10 @@ This cannot be undone!</source>
|
||||
<target>保存用户密码时出错</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error scanning code: %@" xml:space="preserve">
|
||||
<source>Error scanning code: %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error sending email" xml:space="preserve">
|
||||
<source>Error sending email</source>
|
||||
<target>发送电邮错误</target>
|
||||
@@ -2604,8 +2649,8 @@ This cannot be undone!</source>
|
||||
<target>群组成员可以添加信息回应。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group members can irreversibly delete sent messages." xml:space="preserve">
|
||||
<source>Group members can irreversibly delete sent messages.</source>
|
||||
<trans-unit id="Group members can irreversibly delete sent messages. (24 hours)" xml:space="preserve">
|
||||
<source>Group members can irreversibly delete sent messages. (24 hours)</source>
|
||||
<target>群组成员可以不可撤回地删除已发送的消息。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
@@ -2714,6 +2759,10 @@ This cannot be undone!</source>
|
||||
<target>历史记录</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="History is not sent to new members." xml:space="preserve">
|
||||
<source>History is not sent to new members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="How SimpleX works" xml:space="preserve">
|
||||
<source>How SimpleX works</source>
|
||||
<target>SimpleX的工作原理</target>
|
||||
@@ -2749,11 +2798,6 @@ This cannot be undone!</source>
|
||||
<target>如果您不能亲自见面,可以在视频通话中展示二维码,或分享链接。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link." xml:space="preserve">
|
||||
<source>If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link.</source>
|
||||
<target>如果您不能亲自见面,您可以**扫描视频通话中的二维码**,或者您的联系人可以分享邀请链接。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="If you enter this passcode when opening the app, all app data will be irreversibly removed!" xml:space="preserve">
|
||||
<source>If you enter this passcode when opening the app, all app data will be irreversibly removed!</source>
|
||||
<target>如果您在打开应用时输入该密码,所有应用程序数据将被不可撤回地删除!</target>
|
||||
@@ -2809,6 +2853,10 @@ This cannot be undone!</source>
|
||||
<target>导入数据库</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Improved message delivery" xml:space="preserve">
|
||||
<source>Improved message delivery</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Improved privacy and security" xml:space="preserve">
|
||||
<source>Improved privacy and security</source>
|
||||
<target>改进的隐私和安全</target>
|
||||
@@ -2909,15 +2957,31 @@ This cannot be undone!</source>
|
||||
<target>界面</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid QR code" xml:space="preserve">
|
||||
<source>Invalid QR code</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid connection link" xml:space="preserve">
|
||||
<source>Invalid connection link</source>
|
||||
<target>无效的连接链接</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid display name!" xml:space="preserve">
|
||||
<source>Invalid display name!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid link" xml:space="preserve">
|
||||
<source>Invalid link</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid name!" xml:space="preserve">
|
||||
<source>Invalid name!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid response" xml:space="preserve">
|
||||
<source>Invalid response</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid server address!" xml:space="preserve">
|
||||
<source>Invalid server address!</source>
|
||||
<target>无效的服务器地址!</target>
|
||||
@@ -3009,6 +3073,10 @@ This cannot be undone!</source>
|
||||
<target>加入群组</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Join group conversations" xml:space="preserve">
|
||||
<source>Join group conversations</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Join group?" xml:space="preserve">
|
||||
<source>Join group?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -3032,10 +3100,18 @@ This is your link for group %@!</source>
|
||||
<target>加入群组中</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Keep" xml:space="preserve">
|
||||
<source>Keep</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Keep the app open to use it from desktop" xml:space="preserve">
|
||||
<source>Keep the app open to use it from desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Keep unused invitation?" xml:space="preserve">
|
||||
<source>Keep unused invitation?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Keep your connections" xml:space="preserve">
|
||||
<source>Keep your connections</source>
|
||||
<target>保持连接</target>
|
||||
@@ -3118,6 +3194,11 @@ This is your link for group %@!</source>
|
||||
<target>实时消息</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Local" xml:space="preserve">
|
||||
<source>Local</source>
|
||||
<target>本地</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Local name" xml:space="preserve">
|
||||
<source>Local name</source>
|
||||
<target>本地名称</target>
|
||||
@@ -3357,6 +3438,10 @@ This is your link for group %@!</source>
|
||||
<target>新密码</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="New chat" xml:space="preserve">
|
||||
<source>New chat</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="New contact request" xml:space="preserve">
|
||||
<source>New contact request</source>
|
||||
<target>新联系人请求</target>
|
||||
@@ -3480,16 +3565,15 @@ This is your link for group %@!</source>
|
||||
- 禁用成员(“观察员”角色)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="OK" xml:space="preserve">
|
||||
<source>OK</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Off" xml:space="preserve">
|
||||
<source>Off</source>
|
||||
<target>关闭</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Off (Local)" xml:space="preserve">
|
||||
<source>Off (Local)</source>
|
||||
<target>关闭(本地)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Ok" xml:space="preserve">
|
||||
<source>Ok</source>
|
||||
<target>好的</target>
|
||||
@@ -3550,8 +3634,8 @@ This is your link for group %@!</source>
|
||||
<target>只有您可以添加消息回应。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Only you can irreversibly delete messages (your contact can mark them for deletion)." xml:space="preserve">
|
||||
<source>Only you can irreversibly delete messages (your contact can mark them for deletion).</source>
|
||||
<trans-unit id="Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)" xml:space="preserve">
|
||||
<source>Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)</source>
|
||||
<target>只有您可以不可撤回地删除消息(您的联系人可以将它们标记为删除)。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
@@ -3575,8 +3659,8 @@ This is your link for group %@!</source>
|
||||
<target>只有您的联系人可以添加消息回应。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Only your contact can irreversibly delete messages (you can mark them for deletion)." xml:space="preserve">
|
||||
<source>Only your contact can irreversibly delete messages (you can mark them for deletion).</source>
|
||||
<trans-unit id="Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours)" xml:space="preserve">
|
||||
<source>Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours)</source>
|
||||
<target>只有您的联系人才能不可撤回地删除消息(您可以将它们标记为删除)。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
@@ -3629,9 +3713,16 @@ This is your link for group %@!</source>
|
||||
<target>开源协议和代码——任何人都可以运行服务器。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Opening database…" xml:space="preserve">
|
||||
<source>Opening database…</source>
|
||||
<target>打开数据库中……</target>
|
||||
<trans-unit id="Opening app…" xml:space="preserve">
|
||||
<source>Opening app…</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Or scan QR code" xml:space="preserve">
|
||||
<source>Or scan QR code</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Or show this code" xml:space="preserve">
|
||||
<source>Or show this code</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="PING count" xml:space="preserve">
|
||||
@@ -3674,10 +3765,9 @@ This is your link for group %@!</source>
|
||||
<target>显示密码</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste" xml:space="preserve">
|
||||
<source>Paste</source>
|
||||
<target>粘贴</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<trans-unit id="Past member %@" xml:space="preserve">
|
||||
<source>Past member %@</source>
|
||||
<note>past/unknown group member</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste desktop address" xml:space="preserve">
|
||||
<source>Paste desktop address</source>
|
||||
@@ -3688,15 +3778,13 @@ This is your link for group %@!</source>
|
||||
<target>粘贴图片</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste received link" xml:space="preserve">
|
||||
<source>Paste received link</source>
|
||||
<target>粘贴收到的链接</target>
|
||||
<trans-unit id="Paste link to connect!" xml:space="preserve">
|
||||
<source>Paste link to connect!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste the link you received to connect with your contact." xml:space="preserve">
|
||||
<source>Paste the link you received to connect with your contact.</source>
|
||||
<target>将您收到的链接粘贴到下面的框中以与您的联系人联系。</target>
|
||||
<note>placeholder</note>
|
||||
<trans-unit id="Paste the link you received" xml:space="preserve">
|
||||
<source>Paste the link you received</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="People can connect to you only via the links you share." xml:space="preserve">
|
||||
<source>People can connect to you only via the links you share.</source>
|
||||
@@ -3733,6 +3821,11 @@ This is your link for group %@!</source>
|
||||
<target>请检查您和您的联系人偏好设置。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please contact developers. Error: %@" xml:space="preserve">
|
||||
<source>Please contact developers.
|
||||
Error: %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Please contact group admin." xml:space="preserve">
|
||||
<source>Please contact group admin.</source>
|
||||
<target>请联系群组管理员。</target>
|
||||
@@ -3818,6 +3911,10 @@ This is your link for group %@!</source>
|
||||
<target>私密文件名</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Private notes" xml:space="preserve">
|
||||
<source>Private notes</source>
|
||||
<note>name of notes to self</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Profile and server connections" xml:space="preserve">
|
||||
<source>Profile and server connections</source>
|
||||
<target>资料和服务器连接</target>
|
||||
@@ -3936,6 +4033,10 @@ This is your link for group %@!</source>
|
||||
<target>在 [用户指南](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address) 中阅读更多内容。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." xml:space="preserve">
|
||||
<source>Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode).</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." xml:space="preserve">
|
||||
<source>Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends).</source>
|
||||
<target>在 [用户指南](https://simplex.chat/docs/guide/readme.html#connect-to-friends) 中阅读更多内容。</target>
|
||||
@@ -3991,6 +4092,10 @@ This is your link for group %@!</source>
|
||||
<target>接收通过</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Recent history and improved [directory bot](simplex:/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)." xml:space="preserve">
|
||||
<source>Recent history and improved [directory bot](simplex:/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).</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Recipients see updates as you type them." xml:space="preserve">
|
||||
<source>Recipients see updates as you type them.</source>
|
||||
<target>对方会在您键入时看到更新。</target>
|
||||
@@ -4144,6 +4249,10 @@ This is your link for group %@!</source>
|
||||
<target>恢复数据库错误</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Retry" xml:space="preserve">
|
||||
<source>Retry</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reveal" xml:space="preserve">
|
||||
<source>Reveal</source>
|
||||
<target>揭示</target>
|
||||
@@ -4269,6 +4378,10 @@ This is your link for group %@!</source>
|
||||
<target>已保存的WebRTC ICE服务器将被删除</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Saved message" xml:space="preserve">
|
||||
<source>Saved message</source>
|
||||
<note>message info title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Scan QR code" xml:space="preserve">
|
||||
<source>Scan QR code</source>
|
||||
<target>扫描二维码</target>
|
||||
@@ -4298,6 +4411,14 @@ This is your link for group %@!</source>
|
||||
<target>搜索</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search bar accepts invitation links." xml:space="preserve">
|
||||
<source>Search bar accepts invitation 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="Secure queue" xml:space="preserve">
|
||||
<source>Secure queue</source>
|
||||
<target>保护队列</target>
|
||||
@@ -4403,6 +4524,10 @@ This is your link for group %@!</source>
|
||||
<target>发送它们来自图库或自定义键盘。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Send up to 100 last messages to new members." xml:space="preserve">
|
||||
<source>Send up to 100 last messages to new members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sender cancelled file transfer." xml:space="preserve">
|
||||
<source>Sender cancelled file transfer.</source>
|
||||
<target>发送人已取消文件传输。</target>
|
||||
@@ -4572,9 +4697,8 @@ This is your link for group %@!</source>
|
||||
<target>分享链接</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share one-time invitation link" xml:space="preserve">
|
||||
<source>Share one-time invitation link</source>
|
||||
<target>分享一次性邀请链接</target>
|
||||
<trans-unit id="Share this 1-time invite link" xml:space="preserve">
|
||||
<source>Share this 1-time invite link</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Share with contacts" xml:space="preserve">
|
||||
@@ -4697,16 +4821,15 @@ This is your link for group %@!</source>
|
||||
<target>某人</target>
|
||||
<note>notification title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Start a new chat" xml:space="preserve">
|
||||
<source>Start a new chat</source>
|
||||
<target>开始新聊天</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Start chat" xml:space="preserve">
|
||||
<source>Start chat</source>
|
||||
<target>开始聊天</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Start chat?" xml:space="preserve">
|
||||
<source>Start chat?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Start migration" xml:space="preserve">
|
||||
<source>Start migration</source>
|
||||
<target>开始迁移</target>
|
||||
@@ -4831,6 +4954,14 @@ This is your link for group %@!</source>
|
||||
<target>点击以加入隐身聊天</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to paste link" xml:space="preserve">
|
||||
<source>Tap to paste link</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to scan" xml:space="preserve">
|
||||
<source>Tap to scan</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to start a new chat" xml:space="preserve">
|
||||
<source>Tap to start a new chat</source>
|
||||
<target>点击开始一个新聊天</target>
|
||||
@@ -4893,6 +5024,10 @@ It can happen because of some bug or when the connection is compromised.</source
|
||||
<target>更改数据库密码的尝试未完成。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The code you scanned is not a SimpleX link QR code." xml:space="preserve">
|
||||
<source>The code you scanned is not a SimpleX link QR code.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The connection you accepted will be cancelled!" xml:space="preserve">
|
||||
<source>The connection you accepted will be cancelled!</source>
|
||||
<target>您接受的连接将被取消!</target>
|
||||
@@ -4958,21 +5093,15 @@ It can happen because of some bug or when the connection is compromised.</source
|
||||
<target>您当前聊天资料 **%@** 的新连接服务器。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The text you pasted is not a SimpleX link." xml:space="preserve">
|
||||
<source>The text you pasted is not a SimpleX link.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Theme" xml:space="preserve">
|
||||
<source>Theme</source>
|
||||
<target>主题</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="There should be at least one user profile." xml:space="preserve">
|
||||
<source>There should be at least one user profile.</source>
|
||||
<target>应该至少有一个用户资料。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="There should be at least one visible user profile." xml:space="preserve">
|
||||
<source>There should be at least one visible user profile.</source>
|
||||
<target>应该至少有一个可见的用户资料。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="These settings are for your current profile **%@**." xml:space="preserve">
|
||||
<source>These settings are for your current profile **%@**.</source>
|
||||
<target>这些设置适用于您当前的配置文件 **%@**。</target>
|
||||
@@ -5002,6 +5131,10 @@ It can happen because of some bug or when the connection is compromised.</source
|
||||
<source>This device name</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This display name is invalid. Please choose another name." xml:space="preserve">
|
||||
<source>This display name is invalid. Please choose another name.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This group has over %lld members, delivery receipts are not sent." xml:space="preserve">
|
||||
<source>This group has over %lld members, delivery receipts are not sent.</source>
|
||||
<target>该组有超过 %lld 个成员,不发送送货单。</target>
|
||||
@@ -5101,16 +5234,15 @@ You will be prompted to complete authentication before this feature is enabled.<
|
||||
<target>正在尝试连接到用于从该联系人接收消息的服务器。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Turkish interface" xml:space="preserve">
|
||||
<source>Turkish interface</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Turn off" xml:space="preserve">
|
||||
<source>Turn off</source>
|
||||
<target>关闭</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Turn off notifications?" xml:space="preserve">
|
||||
<source>Turn off notifications?</source>
|
||||
<target>关闭通知?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Turn on" xml:space="preserve">
|
||||
<source>Turn on</source>
|
||||
<target>打开</target>
|
||||
@@ -5125,10 +5257,18 @@ You will be prompted to complete authentication before this feature is enabled.<
|
||||
<source>Unblock</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unblock for all" xml:space="preserve">
|
||||
<source>Unblock for all</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unblock member" xml:space="preserve">
|
||||
<source>Unblock member</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unblock member for all?" xml:space="preserve">
|
||||
<source>Unblock member for all?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unblock member?" xml:space="preserve">
|
||||
<source>Unblock member?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -5223,6 +5363,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="Up to 100 last messages are sent to new members." xml:space="preserve">
|
||||
<source>Up to 100 last messages are sent to new members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Update" xml:space="preserve">
|
||||
<source>Update</source>
|
||||
<target>更新</target>
|
||||
@@ -5307,6 +5451,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="Use only local notifications?" xml:space="preserve">
|
||||
<source>Use only local notifications?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Use server" xml:space="preserve">
|
||||
<source>Use server</source>
|
||||
<target>使用服务器</target>
|
||||
@@ -5383,6 +5531,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="Visible history" xml:space="preserve">
|
||||
<source>Visible history</source>
|
||||
<note>chat feature</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Voice messages" xml:space="preserve">
|
||||
<source>Voice messages</source>
|
||||
<target>语音消息</target>
|
||||
@@ -5467,11 +5619,19 @@ 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="With encrypted files and media." xml:space="preserve">
|
||||
<source>With encrypted files and media.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="With optional welcome message." xml:space="preserve">
|
||||
<source>With optional welcome message.</source>
|
||||
<target>带有可选的欢迎消息。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="With reduced battery usage." xml:space="preserve">
|
||||
<source>With reduced battery usage.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Wrong database passphrase" xml:space="preserve">
|
||||
<source>Wrong database passphrase</source>
|
||||
<target>数据库密码错误</target>
|
||||
@@ -5556,11 +5716,6 @@ Repeat join request?</source>
|
||||
<target>您可以从锁屏上接听电话,无需设备和应用程序的认证。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button." xml:space="preserve">
|
||||
<source>You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button.</source>
|
||||
<target>您也可以通过点击链接进行连接。如果在浏览器中打开,请点击“在移动应用程序中打开”按钮。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can create it later" xml:space="preserve">
|
||||
<source>You can create it later</source>
|
||||
<target>您可以以后创建它</target>
|
||||
@@ -5581,6 +5736,10 @@ Repeat join request?</source>
|
||||
<target>您可以隐藏或静音用户个人资料——只需向右滑动。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can make it visible to your SimpleX contacts via Settings." xml:space="preserve">
|
||||
<source>You can make it visible to your SimpleX contacts via Settings.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can now send messages to %@" xml:space="preserve">
|
||||
<source>You can now send messages to %@</source>
|
||||
<target>您现在可以给 %@ 发送消息</target>
|
||||
@@ -5621,6 +5780,10 @@ Repeat join request?</source>
|
||||
<target>您可以使用 markdown 来编排消息格式:</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can view invitation link again in connection details." xml:space="preserve">
|
||||
<source>You can view invitation link again in connection details.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can't send messages!" xml:space="preserve">
|
||||
<source>You can't send messages!</source>
|
||||
<target>您无法发送消息!</target>
|
||||
@@ -5805,13 +5968,6 @@ You can cancel this connection and remove the contact (and try later with a new
|
||||
<target>您的联系人可以允许完全删除消息。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your contacts in SimpleX will see it. You can change it in Settings." xml:space="preserve">
|
||||
<source>Your contacts in SimpleX will see it.
|
||||
You can change it in Settings.</source>
|
||||
<target>您的 SimpleX 的联系人会看到它。
|
||||
您可以在设置中更改它。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your contacts will remain connected." xml:space="preserve">
|
||||
<source>Your contacts will remain connected.</source>
|
||||
<target>与您的联系人保持连接。</target>
|
||||
@@ -5960,6 +6116,14 @@ SimpleX 服务器无法看到您的资料。</target>
|
||||
<source>blocked</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="blocked %@" xml:space="preserve">
|
||||
<source>blocked %@</source>
|
||||
<note>rcv group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="blocked by admin" xml:space="preserve">
|
||||
<source>blocked by admin</source>
|
||||
<note>blocked chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="bold" xml:space="preserve">
|
||||
<source>bold</source>
|
||||
<target>加粗</target>
|
||||
@@ -6080,6 +6244,10 @@ SimpleX 服务器无法看到您的资料。</target>
|
||||
<target>连接:%@</target>
|
||||
<note>connection information</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="contact %@ changed to %@" xml:space="preserve">
|
||||
<source>contact %1$@ changed to %2$@</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="contact has e2e encryption" xml:space="preserve">
|
||||
<source>contact has e2e encryption</source>
|
||||
<target>联系人具有端到端加密</target>
|
||||
@@ -6349,6 +6517,10 @@ SimpleX 服务器无法看到您的资料。</target>
|
||||
<target>成员</target>
|
||||
<note>member role</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="member %@ changed to %@" xml:space="preserve">
|
||||
<source>member %1$@ changed to %2$@</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="member connected" xml:space="preserve">
|
||||
<source>connected</source>
|
||||
<target>已连接</target>
|
||||
@@ -6471,6 +6643,14 @@ SimpleX 服务器无法看到您的资料。</target>
|
||||
<target>已删除 %@</target>
|
||||
<note>rcv group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="removed contact address" xml:space="preserve">
|
||||
<source>removed contact address</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="removed profile picture" xml:space="preserve">
|
||||
<source>removed profile picture</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="removed you" xml:space="preserve">
|
||||
<source>removed you</source>
|
||||
<target>已将您移除</target>
|
||||
@@ -6501,6 +6681,14 @@ SimpleX 服务器无法看到您的资料。</target>
|
||||
<target>发送私信</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="set new contact address" xml:space="preserve">
|
||||
<source>set new contact address</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="set new profile picture" xml:space="preserve">
|
||||
<source>set new profile picture</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="starting…" xml:space="preserve">
|
||||
<source>starting…</source>
|
||||
<target>启动中……</target>
|
||||
@@ -6516,16 +6704,28 @@ SimpleX 服务器无法看到您的资料。</target>
|
||||
<target>这个联系人</target>
|
||||
<note>notification title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="unblocked %@" xml:space="preserve">
|
||||
<source>unblocked %@</source>
|
||||
<note>rcv group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="unknown" xml:space="preserve">
|
||||
<source>unknown</source>
|
||||
<target>未知</target>
|
||||
<note>connection info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="unknown status" xml:space="preserve">
|
||||
<source>unknown status</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="updated group profile" xml:space="preserve">
|
||||
<source>updated group profile</source>
|
||||
<target>已更新的群组资料</target>
|
||||
<note>rcv group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="updated profile" xml:space="preserve">
|
||||
<source>updated profile</source>
|
||||
<note>profile update event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="v%@" xml:space="preserve">
|
||||
<source>v%@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -6595,6 +6795,10 @@ SimpleX 服务器无法看到您的资料。</target>
|
||||
<target>您是观察者</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="you blocked %@" xml:space="preserve">
|
||||
<source>you blocked %@</source>
|
||||
<note>snd group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="you changed address" xml:space="preserve">
|
||||
<source>you changed address</source>
|
||||
<target>您已更改地址</target>
|
||||
@@ -6635,6 +6839,10 @@ SimpleX 服务器无法看到您的资料。</target>
|
||||
<target>您分享了一次性链接隐身聊天</target>
|
||||
<note>chat list item description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="you unblocked %@" xml:space="preserve">
|
||||
<source>you unblocked %@</source>
|
||||
<note>snd group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="you: " xml:space="preserve">
|
||||
<source>you: </source>
|
||||
<target>您: </target>
|
||||
|
||||
@@ -5868,6 +5868,36 @@ It can happen because of some bug or when the connection is compromised.</source
|
||||
<target state="translated">你和你的聯絡人可以新增訊息互動。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ connected" xml:space="preserve" approved="no">
|
||||
<source>%@ connected</source>
|
||||
<target state="translated">%@ 已連接</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="# %@" xml:space="preserve" approved="no">
|
||||
<source># %@</source>
|
||||
<target state="translated"># %@</target>
|
||||
<note>copied message info title, # <title></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ and %@" xml:space="preserve" approved="no">
|
||||
<source>%@ and %@</source>
|
||||
<target state="translated">%@ 和 %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="## History" xml:space="preserve" approved="no">
|
||||
<source>## History</source>
|
||||
<target state="translated">紀錄</target>
|
||||
<note>copied message info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="## In reply to" xml:space="preserve" approved="no">
|
||||
<source>## In reply to</source>
|
||||
<target state="translated">回覆</target>
|
||||
<note>copied message info</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ and %@ connected" xml:space="preserve" approved="no">
|
||||
<source>%@ and %@ connected</source>
|
||||
<target state="translated">%@ 和 %@ 已連接</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="zh-Hant" datatype="plaintext">
|
||||
|
||||
@@ -453,7 +453,6 @@ var receiverStarted = false
|
||||
let startLock = DispatchSemaphore(value: 1)
|
||||
let suspendLock = DispatchSemaphore(value: 1)
|
||||
var networkConfig: NetCfg = getNetCfg()
|
||||
let xftpConfig: XFTPFileConfig? = getXFTPCfg()
|
||||
|
||||
// startChat uses semaphore startLock to ensure that only one didReceive thread can start chat controller
|
||||
// Subsequent calls to didReceive will be waiting on semaphore and won't start chat again, as it will be .active
|
||||
@@ -499,7 +498,6 @@ func doStartChat() -> DBMigrationResult? {
|
||||
try setNetworkConfig(networkConfig)
|
||||
try apiSetTempFolder(tempFolder: getTempFilesDirectory().path)
|
||||
try apiSetFilesFolder(filesFolder: getAppFilesDirectory().path)
|
||||
try setXFTPConfig(xftpConfig)
|
||||
try apiSetEncryptLocalFiles(privacyEncryptLocalFilesGroupDefault.get())
|
||||
// prevent suspension while starting chat
|
||||
suspendLock.wait()
|
||||
@@ -631,7 +629,7 @@ func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotification)? {
|
||||
ntfBadgeCountGroupDefault.set(max(0, ntfBadgeCountGroupDefault.get() - 1))
|
||||
}
|
||||
if let file = cItem.autoReceiveFile() {
|
||||
cItem = autoReceiveFile(file, encrypted: cItem.encryptLocalFile) ?? cItem
|
||||
cItem = autoReceiveFile(file) ?? cItem
|
||||
}
|
||||
let ntf: NSENotification = cInfo.ntfsEnabled ? .nse(createMessageReceivedNtf(user, cInfo, cItem)) : .empty
|
||||
return cItem.showNotification ? (aChatItem.chatId, ntf) : nil
|
||||
@@ -733,12 +731,6 @@ func apiSetFilesFolder(filesFolder: String) throws {
|
||||
throw r
|
||||
}
|
||||
|
||||
func setXFTPConfig(_ cfg: XFTPFileConfig?) throws {
|
||||
let r = sendSimpleXCmd(.apiSetXFTPConfig(config: cfg))
|
||||
if case .cmdOk = r { return }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiSetEncryptLocalFiles(_ enable: Bool) throws {
|
||||
let r = sendSimpleXCmd(.apiSetEncryptLocalFiles(enable: enable))
|
||||
if case .cmdOk = r { return }
|
||||
@@ -775,13 +767,16 @@ func apiSetFileToReceive(fileId: Int64, encrypted: Bool) {
|
||||
logger.error("setFileToReceive error: \(responseError(r))")
|
||||
}
|
||||
|
||||
func autoReceiveFile(_ file: CIFile, encrypted: Bool) -> ChatItem? {
|
||||
func autoReceiveFile(_ file: CIFile) -> ChatItem? {
|
||||
let encrypted = privacyEncryptLocalFilesGroupDefault.get()
|
||||
switch file.fileProtocol {
|
||||
case .smp:
|
||||
return apiReceiveFile(fileId: file.fileId, encrypted: encrypted)?.chatItem
|
||||
case .xftp:
|
||||
apiSetFileToReceive(fileId: file.fileId, encrypted: encrypted)
|
||||
return nil
|
||||
case .local:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
9
apps/ios/SimpleX NSE/tr.lproj/InfoPlist.strings
Normal file
9
apps/ios/SimpleX NSE/tr.lproj/InfoPlist.strings
Normal file
@@ -0,0 +1,9 @@
|
||||
/* Bundle display name */
|
||||
"CFBundleDisplayName" = "SimpleX NSE";
|
||||
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "SimpleX NSE";
|
||||
|
||||
/* Copyright (human-readable) */
|
||||
"NSHumanReadableCopyright" = "Telif Hakkı © 2024 SimpleX Chat. Tüm hakları saklıdır.";
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
18415C6C56DBCEC2CBBD2F11 /* WebRTCClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18415323A4082FC92887F906 /* WebRTCClient.swift */; };
|
||||
18415F9A2D551F9757DA4654 /* CIVideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18415FD2E36F13F596A45BB4 /* CIVideoView.swift */; };
|
||||
18415FEFE153C5920BFB7828 /* GroupWelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1841516F0CE5992B0EDFB377 /* GroupWelcomeView.swift */; };
|
||||
3C8C548928133C84000A3EC7 /* PasteToConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C8C548828133C84000A3EC7 /* PasteToConnectView.swift */; };
|
||||
3CDBCF4227FAE51000354CDD /* ComposeLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CDBCF4127FAE51000354CDD /* ComposeLinkView.swift */; };
|
||||
3CDBCF4827FF621E00354CDD /* CILinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CDBCF4727FF621E00354CDD /* CILinkView.swift */; };
|
||||
5C00164428A26FBC0094D739 /* ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00164328A26FBC0094D739 /* ContextMenu.swift */; };
|
||||
@@ -30,11 +29,6 @@
|
||||
5C116CDC27AABE0400E66D01 /* ContactRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */; };
|
||||
5C13730B28156D2700F43030 /* ContactConnectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C13730A28156D2700F43030 /* ContactConnectionView.swift */; };
|
||||
5C1A4C1E27A715B700EAD5AD /* ChatItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */; };
|
||||
5C245F232B4EAA5E001CC39F /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C245F1E2B4EAA5E001CC39F /* libgmpxx.a */; };
|
||||
5C245F242B4EAA5E001CC39F /* libHSsimplex-chat-5.4.3.0-BrNBQIZf2Ju9TtaZoeJyIH-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C245F1F2B4EAA5E001CC39F /* libHSsimplex-chat-5.4.3.0-BrNBQIZf2Ju9TtaZoeJyIH-ghc9.6.3.a */; };
|
||||
5C245F252B4EAA5E001CC39F /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C245F202B4EAA5E001CC39F /* libgmp.a */; };
|
||||
5C245F262B4EAA5E001CC39F /* libHSsimplex-chat-5.4.3.0-BrNBQIZf2Ju9TtaZoeJyIH.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C245F212B4EAA5E001CC39F /* libHSsimplex-chat-5.4.3.0-BrNBQIZf2Ju9TtaZoeJyIH.a */; };
|
||||
5C245F272B4EAA5E001CC39F /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C245F222B4EAA5E001CC39F /* libffi.a */; };
|
||||
5C2E260727A2941F00F70299 /* SimpleXAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260627A2941F00F70299 /* SimpleXAPI.swift */; };
|
||||
5C2E260B27A30CFA00F70299 /* ChatListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260A27A30CFA00F70299 /* ChatListView.swift */; };
|
||||
5C2E260F27A30FDC00F70299 /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260E27A30FDC00F70299 /* ChatView.swift */; };
|
||||
@@ -61,7 +55,6 @@
|
||||
5C5F2B7027EBC704006A9D5F /* ProfileImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5F2B6F27EBC704006A9D5F /* ProfileImage.swift */; };
|
||||
5C65DAF929D0CC20003CEE45 /* DeveloperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C65DAF829D0CC20003CEE45 /* DeveloperView.swift */; };
|
||||
5C65F343297D45E100B67AF3 /* VersionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C65F341297D3F3600B67AF3 /* VersionView.swift */; };
|
||||
5C6AD81327A834E300348BD7 /* NewChatButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6AD81227A834E300348BD7 /* NewChatButton.swift */; };
|
||||
5C6BA667289BD954009B8ECC /* DismissSheets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6BA666289BD954009B8ECC /* DismissSheets.swift */; };
|
||||
5C7031162953C97F00150A12 /* CIFeaturePreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7031152953C97F00150A12 /* CIFeaturePreferenceView.swift */; };
|
||||
5C7505A227B65FDB00BE3227 /* CIMetaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7505A127B65FDB00BE3227 /* CIMetaView.swift */; };
|
||||
@@ -97,9 +90,12 @@
|
||||
5CB0BA90282713D900B3292C /* SimpleXInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB0BA8F282713D900B3292C /* SimpleXInfo.swift */; };
|
||||
5CB0BA92282713FD00B3292C /* CreateProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB0BA91282713FD00B3292C /* CreateProfile.swift */; };
|
||||
5CB0BA9A2827FD8800B3292C /* HowItWorks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB0BA992827FD8800B3292C /* HowItWorks.swift */; };
|
||||
5CB1CE922B86660100963938 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CB1CE8D2B86660100963938 /* libgmp.a */; };
|
||||
5CB1CE932B86660100963938 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CB1CE8E2B86660100963938 /* libgmpxx.a */; };
|
||||
5CB1CE942B86660100963938 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CB1CE8F2B86660100963938 /* libffi.a */; };
|
||||
5CB1CE952B86660100963938 /* libHSsimplex-chat-5.5.5.0-7lQXtpK7ThcLvpQEItJUcV-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CB1CE902B86660100963938 /* libHSsimplex-chat-5.5.5.0-7lQXtpK7ThcLvpQEItJUcV-ghc9.6.3.a */; };
|
||||
5CB1CE962B86660100963938 /* libHSsimplex-chat-5.5.5.0-7lQXtpK7ThcLvpQEItJUcV.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CB1CE912B86660100963938 /* libHSsimplex-chat-5.5.5.0-7lQXtpK7ThcLvpQEItJUcV.a */; };
|
||||
5CB2084F28DA4B4800D024EC /* RTCServers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB2084E28DA4B4800D024EC /* RTCServers.swift */; };
|
||||
5CB2085128DB64CA00D024EC /* CreateLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB2085028DB64CA00D024EC /* CreateLinkView.swift */; };
|
||||
5CB2085328DB7CAF00D024EC /* ConnectViaLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB2085228DB7CAF00D024EC /* ConnectViaLinkView.swift */; };
|
||||
5CB346E52868AA7F001FD2EF /* SuspendChat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB346E42868AA7F001FD2EF /* SuspendChat.swift */; };
|
||||
5CB346E72868D76D001FD2EF /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB346E62868D76D001FD2EF /* NotificationsView.swift */; };
|
||||
5CB346E92869E8BA001FD2EF /* PushEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB346E82869E8BA001FD2EF /* PushEnvironment.swift */; };
|
||||
@@ -121,8 +117,6 @@
|
||||
5CC2C0FF2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CC2C0FD2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings */; };
|
||||
5CC868F329EB540C0017BBFD /* CIRcvDecryptionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */; };
|
||||
5CCB939C297EFCB100399E78 /* NavStackCompat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCB939B297EFCB100399E78 /* NavStackCompat.swift */; };
|
||||
5CCD403427A5F6DF00368C90 /* AddContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403327A5F6DF00368C90 /* AddContactView.swift */; };
|
||||
5CCD403727A5F9A200368C90 /* ScanToConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */; };
|
||||
5CD67B8F2B0E858A00C510B1 /* hs_init.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CD67B8D2B0E858A00C510B1 /* hs_init.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5CD67B902B0E858A00C510B1 /* hs_init.c in Sources */ = {isa = PBXBuildFile; fileRef = 5CD67B8E2B0E858A00C510B1 /* hs_init.c */; };
|
||||
5CDCAD482818589900503DA2 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CDCAD472818589900503DA2 /* NotificationService.swift */; };
|
||||
@@ -157,6 +151,8 @@
|
||||
5CFA59D12864782E00863A68 /* ChatArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFA59CF286477B400863A68 /* ChatArchiveView.swift */; };
|
||||
5CFE0921282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; };
|
||||
5CFE0922282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; };
|
||||
640417CD2B29B8C200CCB412 /* NewChatMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640417CB2B29B8C200CCB412 /* NewChatMenuButton.swift */; };
|
||||
640417CE2B29B8C200CCB412 /* NewChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640417CC2B29B8C200CCB412 /* NewChatView.swift */; };
|
||||
6407BA83295DA85D0082BA18 /* CIInvalidJSONView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */; };
|
||||
6419EC562AB8BC8B004A607A /* ContextInvitingContactMemberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */; };
|
||||
6419EC582AB97507004A607A /* CIMemberCreatedContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */; };
|
||||
@@ -168,11 +164,6 @@
|
||||
64466DC829FC2B3B00E3D48D /* CreateSimpleXAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64466DC729FC2B3B00E3D48D /* CreateSimpleXAddress.swift */; };
|
||||
64466DCC29FFE3E800E3D48D /* MailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64466DCB29FFE3E800E3D48D /* MailView.swift */; };
|
||||
6448BBB628FA9D56000D2AB9 /* GroupLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */; };
|
||||
6449333A2AF8E51000AC506E /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933352AF8E51000AC506E /* libgmpxx.a */; };
|
||||
6449333B2AF8E51000AC506E /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933362AF8E51000AC506E /* libgmp.a */; };
|
||||
6449333C2AF8E51000AC506E /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933372AF8E51000AC506E /* libffi.a */; };
|
||||
6449333D2AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933382AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8-ghc9.6.3.a */; };
|
||||
6449333E2AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933392AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8.a */; };
|
||||
644EFFDE292BCD9D00525D5B /* ComposeVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */; };
|
||||
644EFFE0292CFD7F00525D5B /* CIVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFDF292CFD7F00525D5B /* CIVoiceView.swift */; };
|
||||
644EFFE2292D089800525D5B /* FramedCIVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFE1292D089800525D5B /* FramedCIVoiceView.swift */; };
|
||||
@@ -268,7 +259,6 @@
|
||||
18415B08031E8FB0F7FC27F9 /* CallViewRenderers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallViewRenderers.swift; sourceTree = "<group>"; };
|
||||
18415DAAAD1ADBEDB0EDA852 /* VideoPlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoPlayerView.swift; sourceTree = "<group>"; };
|
||||
18415FD2E36F13F596A45BB4 /* CIVideoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CIVideoView.swift; sourceTree = "<group>"; };
|
||||
3C8C548828133C84000A3EC7 /* PasteToConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteToConnectView.swift; sourceTree = "<group>"; };
|
||||
3CDBCF4127FAE51000354CDD /* ComposeLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeLinkView.swift; sourceTree = "<group>"; };
|
||||
3CDBCF4727FF621E00354CDD /* CILinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CILinkView.swift; sourceTree = "<group>"; };
|
||||
5C00164328A26FBC0094D739 /* ContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenu.swift; sourceTree = "<group>"; };
|
||||
@@ -285,11 +275,9 @@
|
||||
5C13730A28156D2700F43030 /* ContactConnectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactConnectionView.swift; sourceTree = "<group>"; };
|
||||
5C13730C2815740A00F43030 /* DebugJSON.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = DebugJSON.playground; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
|
||||
5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemView.swift; sourceTree = "<group>"; };
|
||||
5C245F1E2B4EAA5E001CC39F /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5C245F1F2B4EAA5E001CC39F /* libHSsimplex-chat-5.4.3.0-BrNBQIZf2Ju9TtaZoeJyIH-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.3.0-BrNBQIZf2Ju9TtaZoeJyIH-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
5C245F202B4EAA5E001CC39F /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5C245F212B4EAA5E001CC39F /* libHSsimplex-chat-5.4.3.0-BrNBQIZf2Ju9TtaZoeJyIH.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.3.0-BrNBQIZf2Ju9TtaZoeJyIH.a"; sourceTree = "<group>"; };
|
||||
5C245F222B4EAA5E001CC39F /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5C245F3C2B501E98001CC39F /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
5C245F3D2B501F13001CC39F /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = "tr.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
5C245F3E2B501F13001CC39F /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
5C2E260627A2941F00F70299 /* SimpleXAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleXAPI.swift; sourceTree = "<group>"; };
|
||||
5C2E260A27A30CFA00F70299 /* ChatListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListView.swift; sourceTree = "<group>"; };
|
||||
5C2E260E27A30FDC00F70299 /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = "<group>"; };
|
||||
@@ -329,7 +317,6 @@
|
||||
5C65DAED29CB8908003CEE45 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
5C65DAF829D0CC20003CEE45 /* DeveloperView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperView.swift; sourceTree = "<group>"; };
|
||||
5C65F341297D3F3600B67AF3 /* VersionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionView.swift; sourceTree = "<group>"; };
|
||||
5C6AD81227A834E300348BD7 /* NewChatButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewChatButton.swift; sourceTree = "<group>"; };
|
||||
5C6BA666289BD954009B8ECC /* DismissSheets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissSheets.swift; sourceTree = "<group>"; };
|
||||
5C6D183229E93FBA00D430B3 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = "pl.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
5C6D183329E93FBA00D430B3 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
@@ -385,9 +372,12 @@
|
||||
5CB0BA8F282713D900B3292C /* SimpleXInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleXInfo.swift; sourceTree = "<group>"; };
|
||||
5CB0BA91282713FD00B3292C /* CreateProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateProfile.swift; sourceTree = "<group>"; };
|
||||
5CB0BA992827FD8800B3292C /* HowItWorks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HowItWorks.swift; sourceTree = "<group>"; };
|
||||
5CB1CE8D2B86660100963938 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5CB1CE8E2B86660100963938 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5CB1CE8F2B86660100963938 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5CB1CE902B86660100963938 /* libHSsimplex-chat-5.5.5.0-7lQXtpK7ThcLvpQEItJUcV-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.5.5.0-7lQXtpK7ThcLvpQEItJUcV-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
5CB1CE912B86660100963938 /* libHSsimplex-chat-5.5.5.0-7lQXtpK7ThcLvpQEItJUcV.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.5.5.0-7lQXtpK7ThcLvpQEItJUcV.a"; sourceTree = "<group>"; };
|
||||
5CB2084E28DA4B4800D024EC /* RTCServers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RTCServers.swift; sourceTree = "<group>"; };
|
||||
5CB2085028DB64CA00D024EC /* CreateLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateLinkView.swift; sourceTree = "<group>"; };
|
||||
5CB2085228DB7CAF00D024EC /* ConnectViaLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectViaLinkView.swift; sourceTree = "<group>"; };
|
||||
5CB2085428DE647400D024EC /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
5CB346E42868AA7F001FD2EF /* SuspendChat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuspendChat.swift; sourceTree = "<group>"; };
|
||||
5CB346E62868D76D001FD2EF /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = "<group>"; };
|
||||
@@ -413,8 +403,6 @@
|
||||
5CC2C0FE2809BF11000C35E3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = "ru.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIRcvDecryptionError.swift; sourceTree = "<group>"; };
|
||||
5CCB939B297EFCB100399E78 /* NavStackCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavStackCompat.swift; sourceTree = "<group>"; };
|
||||
5CCD403327A5F6DF00368C90 /* AddContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactView.swift; sourceTree = "<group>"; };
|
||||
5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanToConnectView.swift; sourceTree = "<group>"; };
|
||||
5CD67B8D2B0E858A00C510B1 /* hs_init.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = hs_init.h; sourceTree = "<group>"; };
|
||||
5CD67B8E2B0E858A00C510B1 /* hs_init.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = hs_init.c; sourceTree = "<group>"; };
|
||||
5CDCAD452818589900503DA2 /* SimpleX NSE.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "SimpleX NSE.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -449,6 +437,8 @@
|
||||
5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateToAppGroupView.swift; sourceTree = "<group>"; };
|
||||
5CFA59CF286477B400863A68 /* ChatArchiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatArchiveView.swift; sourceTree = "<group>"; };
|
||||
5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ZoomableScrollView.swift; path = Shared/Views/ZoomableScrollView.swift; sourceTree = SOURCE_ROOT; };
|
||||
640417CB2B29B8C200CCB412 /* NewChatMenuButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewChatMenuButton.swift; sourceTree = "<group>"; };
|
||||
640417CC2B29B8C200CCB412 /* NewChatView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewChatView.swift; sourceTree = "<group>"; };
|
||||
6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIInvalidJSONView.swift; sourceTree = "<group>"; };
|
||||
6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextInvitingContactMemberView.swift; sourceTree = "<group>"; };
|
||||
6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMemberCreatedContactView.swift; sourceTree = "<group>"; };
|
||||
@@ -460,11 +450,6 @@
|
||||
64466DC729FC2B3B00E3D48D /* CreateSimpleXAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSimpleXAddress.swift; sourceTree = "<group>"; };
|
||||
64466DCB29FFE3E800E3D48D /* MailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailView.swift; sourceTree = "<group>"; };
|
||||
6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupLinkView.swift; sourceTree = "<group>"; };
|
||||
644933352AF8E51000AC506E /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
644933362AF8E51000AC506E /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
644933372AF8E51000AC506E /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
644933382AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
644933392AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8.a"; sourceTree = "<group>"; };
|
||||
644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeVoiceView.swift; sourceTree = "<group>"; };
|
||||
644EFFDF292CFD7F00525D5B /* CIVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIVoiceView.swift; sourceTree = "<group>"; };
|
||||
644EFFE1292D089800525D5B /* FramedCIVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FramedCIVoiceView.swift; sourceTree = "<group>"; };
|
||||
@@ -529,13 +514,13 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5C245F232B4EAA5E001CC39F /* libgmpxx.a in Frameworks */,
|
||||
5C245F262B4EAA5E001CC39F /* libHSsimplex-chat-5.4.3.0-BrNBQIZf2Ju9TtaZoeJyIH.a in Frameworks */,
|
||||
5C245F252B4EAA5E001CC39F /* libgmp.a in Frameworks */,
|
||||
5CB1CE932B86660100963938 /* libgmpxx.a in Frameworks */,
|
||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||
5C245F272B4EAA5E001CC39F /* libffi.a in Frameworks */,
|
||||
5CB1CE962B86660100963938 /* libHSsimplex-chat-5.5.5.0-7lQXtpK7ThcLvpQEItJUcV.a in Frameworks */,
|
||||
5CB1CE922B86660100963938 /* libgmp.a in Frameworks */,
|
||||
5CB1CE952B86660100963938 /* libHSsimplex-chat-5.5.5.0-7lQXtpK7ThcLvpQEItJUcV-ghc9.6.3.a in Frameworks */,
|
||||
5CB1CE942B86660100963938 /* libffi.a in Frameworks */,
|
||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||
5C245F242B4EAA5E001CC39F /* libHSsimplex-chat-5.4.3.0-BrNBQIZf2Ju9TtaZoeJyIH-ghc9.6.3.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -597,11 +582,11 @@
|
||||
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5C245F222B4EAA5E001CC39F /* libffi.a */,
|
||||
5C245F202B4EAA5E001CC39F /* libgmp.a */,
|
||||
5C245F1E2B4EAA5E001CC39F /* libgmpxx.a */,
|
||||
5C245F1F2B4EAA5E001CC39F /* libHSsimplex-chat-5.4.3.0-BrNBQIZf2Ju9TtaZoeJyIH-ghc9.6.3.a */,
|
||||
5C245F212B4EAA5E001CC39F /* libHSsimplex-chat-5.4.3.0-BrNBQIZf2Ju9TtaZoeJyIH.a */,
|
||||
5CB1CE8F2B86660100963938 /* libffi.a */,
|
||||
5CB1CE8D2B86660100963938 /* libgmp.a */,
|
||||
5CB1CE8E2B86660100963938 /* libgmpxx.a */,
|
||||
5CB1CE902B86660100963938 /* libHSsimplex-chat-5.5.5.0-7lQXtpK7ThcLvpQEItJUcV-ghc9.6.3.a */,
|
||||
5CB1CE912B86660100963938 /* libHSsimplex-chat-5.5.5.0-7lQXtpK7ThcLvpQEItJUcV.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
@@ -747,14 +732,10 @@
|
||||
5CB924DD27A8622200ACCCDD /* NewChat */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5C6AD81227A834E300348BD7 /* NewChatButton.swift */,
|
||||
5CCD403327A5F6DF00368C90 /* AddContactView.swift */,
|
||||
5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */,
|
||||
3C8C548828133C84000A3EC7 /* PasteToConnectView.swift */,
|
||||
640417CB2B29B8C200CCB412 /* NewChatMenuButton.swift */,
|
||||
640417CC2B29B8C200CCB412 /* NewChatView.swift */,
|
||||
5CC1C99127A6C7F5000D9FF6 /* QRCode.swift */,
|
||||
6442E0B9287F169300CEC0F9 /* AddGroupView.swift */,
|
||||
5CB2085028DB64CA00D024EC /* CreateLinkView.swift */,
|
||||
5CB2085228DB7CAF00D024EC /* ConnectViaLinkView.swift */,
|
||||
64D0C2C529FAC1EC00B38D5F /* AddContactLearnMore.swift */,
|
||||
);
|
||||
path = NewChat;
|
||||
@@ -1061,6 +1042,7 @@
|
||||
fi,
|
||||
uk,
|
||||
bg,
|
||||
tr,
|
||||
);
|
||||
mainGroup = 5CA059BD279559F40002BEB4;
|
||||
packageReferences = (
|
||||
@@ -1123,8 +1105,8 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
64C06EB52A0A4A7C00792D4D /* ChatItemInfoView.swift in Sources */,
|
||||
640417CE2B29B8C200CCB412 /* NewChatView.swift in Sources */,
|
||||
6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */,
|
||||
5C6AD81327A834E300348BD7 /* NewChatButton.swift in Sources */,
|
||||
5C3F1D58284363C400EC8A82 /* PrivacySettings.swift in Sources */,
|
||||
5C55A923283CEDE600C4E99E /* SoundPlayer.swift in Sources */,
|
||||
5C93292F29239A170090FFF9 /* ProtocolServersView.swift in Sources */,
|
||||
@@ -1171,13 +1153,11 @@
|
||||
5C063D2727A4564100AEC577 /* ChatPreviewView.swift in Sources */,
|
||||
5CC868F329EB540C0017BBFD /* CIRcvDecryptionError.swift in Sources */,
|
||||
5C35CFCB27B2E91D00FB6C6D /* NtfManager.swift in Sources */,
|
||||
3C8C548928133C84000A3EC7 /* PasteToConnectView.swift in Sources */,
|
||||
5C9D13A3282187BB00AB8B43 /* WebRTC.swift in Sources */,
|
||||
5C9A5BDB2871E05400A5B906 /* SetNotificationsMode.swift in Sources */,
|
||||
5CB0BA8E2827126500B3292C /* OnboardingView.swift in Sources */,
|
||||
6442E0BE2880182D00CEC0F9 /* GroupChatInfoView.swift in Sources */,
|
||||
5C2E261227A30FEA00F70299 /* TerminalView.swift in Sources */,
|
||||
5CB2085128DB64CA00D024EC /* CreateLinkView.swift in Sources */,
|
||||
5C9FD96E27A5D6ED0075386C /* SendMessageView.swift in Sources */,
|
||||
5CA7DFC329302AF000F7FDDE /* AppSheet.swift in Sources */,
|
||||
64E972072881BB22008DBC02 /* CIGroupInvitationView.swift in Sources */,
|
||||
@@ -1186,8 +1166,8 @@
|
||||
5CB9250D27A9432000ACCCDD /* ChatListNavLink.swift in Sources */,
|
||||
649BCDA0280460FD00C3A862 /* ComposeImageView.swift in Sources */,
|
||||
5CA059ED279559F40002BEB4 /* ContentView.swift in Sources */,
|
||||
5CCD403427A5F6DF00368C90 /* AddContactView.swift in Sources */,
|
||||
5C05DF532840AA1D00C683F9 /* CallSettings.swift in Sources */,
|
||||
640417CD2B29B8C200CCB412 /* NewChatMenuButton.swift in Sources */,
|
||||
5CFE0921282EEAF60002594B /* ZoomableScrollView.swift in Sources */,
|
||||
5C3A88CE27DF50170060F1C2 /* DetermineWidth.swift in Sources */,
|
||||
5C7505A527B679EE00BE3227 /* NavLinkPlain.swift in Sources */,
|
||||
@@ -1222,7 +1202,6 @@
|
||||
6448BBB628FA9D56000D2AB9 /* GroupLinkView.swift in Sources */,
|
||||
5CB346E92869E8BA001FD2EF /* PushEnvironment.swift in Sources */,
|
||||
5C55A91F283AD0E400C4E99E /* CallManager.swift in Sources */,
|
||||
5CCD403727A5F9A200368C90 /* ScanToConnectView.swift in Sources */,
|
||||
5CFA59D12864782E00863A68 /* ChatArchiveView.swift in Sources */,
|
||||
649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */,
|
||||
8C05382E2B39887E006436DC /* VideoUtils.swift in Sources */,
|
||||
@@ -1245,7 +1224,6 @@
|
||||
5C93293F2928E0FD0090FFF9 /* AudioRecPlay.swift in Sources */,
|
||||
5C029EA82837DBB3004A9677 /* CICallItemView.swift in Sources */,
|
||||
5CE4407227ADB1D0007B033A /* Emoji.swift in Sources */,
|
||||
5CB2085328DB7CAF00D024EC /* ConnectViaLinkView.swift in Sources */,
|
||||
5C9CC7A928C532AB00BEF955 /* DatabaseErrorView.swift in Sources */,
|
||||
5C1A4C1E27A715B700EAD5AD /* ChatItemView.swift in Sources */,
|
||||
64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */,
|
||||
@@ -1352,6 +1330,7 @@
|
||||
5C136D8F2AAB3D14006DE2FC /* fi */,
|
||||
5C636F672AAB3D2400751C84 /* uk */,
|
||||
5C5B67932ABAF56000DA9412 /* bg */,
|
||||
5C245F3E2B501F13001CC39F /* tr */,
|
||||
);
|
||||
name = InfoPlist.strings;
|
||||
sourceTree = "<group>";
|
||||
@@ -1374,6 +1353,7 @@
|
||||
5CE6C7B32AAB1515007F345C /* fi */,
|
||||
5CE6C7B42AAB1527007F345C /* uk */,
|
||||
5C5B67912ABAF4B500DA9412 /* bg */,
|
||||
5C245F3C2B501E98001CC39F /* tr */,
|
||||
);
|
||||
name = Localizable.strings;
|
||||
sourceTree = "<group>";
|
||||
@@ -1395,6 +1375,7 @@
|
||||
5C136D8E2AAB3D14006DE2FC /* fi */,
|
||||
5C636F662AAB3D2400751C84 /* uk */,
|
||||
5C5B67922ABAF56000DA9412 /* bg */,
|
||||
5C245F3D2B501F13001CC39F /* tr */,
|
||||
);
|
||||
name = "SimpleX--iOS--InfoPlist.strings";
|
||||
sourceTree = "<group>";
|
||||
@@ -1528,7 +1509,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 188;
|
||||
CURRENT_PROJECT_VERSION = 200;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1550,7 +1531,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.4.3;
|
||||
MARKETING_VERSION = 5.5.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1571,7 +1552,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 188;
|
||||
CURRENT_PROJECT_VERSION = 200;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1593,7 +1574,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.4.3;
|
||||
MARKETING_VERSION = 5.5.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1652,7 +1633,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 188;
|
||||
CURRENT_PROJECT_VERSION = 200;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -1665,7 +1646,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.4.3;
|
||||
MARKETING_VERSION = 5.5.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -1684,7 +1665,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 188;
|
||||
CURRENT_PROJECT_VERSION = 200;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -1697,7 +1678,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.4.3;
|
||||
MARKETING_VERSION = 5.5.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -1716,7 +1697,7 @@
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 188;
|
||||
CURRENT_PROJECT_VERSION = 200;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1740,7 +1721,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Libraries/sim",
|
||||
);
|
||||
MARKETING_VERSION = 5.4.3;
|
||||
MARKETING_VERSION = 5.5.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1762,7 +1743,7 @@
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 188;
|
||||
CURRENT_PROJECT_VERSION = 200;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1786,7 +1767,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Libraries/sim",
|
||||
);
|
||||
MARKETING_VERSION = 5.4.3;
|
||||
MARKETING_VERSION = 5.5.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
|
||||
@@ -105,6 +105,11 @@ public func parseSimpleXMarkdown(_ s: String) -> [FormattedText]? {
|
||||
return nil
|
||||
}
|
||||
|
||||
public func chatJsonLength(_ s: String) -> Int {
|
||||
var c = s.cString(using: .utf8)!
|
||||
return Int(chat_json_length(&c))
|
||||
}
|
||||
|
||||
struct ParsedMarkdown: Decodable {
|
||||
var formattedText: [FormattedText]?
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ public enum ChatCommand {
|
||||
case apiSuspendChat(timeoutMicroseconds: Int)
|
||||
case setTempFolder(tempFolder: String)
|
||||
case setFilesFolder(filesFolder: String)
|
||||
case apiSetXFTPConfig(config: XFTPFileConfig?)
|
||||
case apiSetEncryptLocalFiles(enable: Bool)
|
||||
case apiExportArchive(config: ArchiveConfig)
|
||||
case apiImportArchive(config: ArchiveConfig)
|
||||
@@ -41,6 +40,7 @@ public enum ChatCommand {
|
||||
case apiGetChat(type: ChatType, id: Int64, pagination: ChatPagination, search: String)
|
||||
case apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64)
|
||||
case apiSendMessage(type: ChatType, id: Int64, file: CryptoFile?, quotedItemId: Int64?, msg: MsgContent, live: Bool, ttl: Int?)
|
||||
case apiCreateChatItem(noteFolderId: Int64, file: CryptoFile?, msg: MsgContent)
|
||||
case apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent, live: Bool)
|
||||
case apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteMode)
|
||||
case apiDeleteMemberChatItem(groupId: Int64, groupMemberId: Int64, itemId: Int64)
|
||||
@@ -54,6 +54,7 @@ public enum ChatCommand {
|
||||
case apiAddMember(groupId: Int64, contactId: Int64, memberRole: GroupMemberRole)
|
||||
case apiJoinGroup(groupId: Int64)
|
||||
case apiMemberRole(groupId: Int64, memberId: Int64, memberRole: GroupMemberRole)
|
||||
case apiBlockMemberForAll(groupId: Int64, memberId: Int64, blocked: Bool)
|
||||
case apiRemoveMember(groupId: Int64, memberId: Int64)
|
||||
case apiLeaveGroup(groupId: Int64)
|
||||
case apiListMembers(groupId: Int64)
|
||||
@@ -160,11 +161,6 @@ public enum ChatCommand {
|
||||
case let .apiSuspendChat(timeoutMicroseconds): return "/_app suspend \(timeoutMicroseconds)"
|
||||
case let .setTempFolder(tempFolder): return "/_temp_folder \(tempFolder)"
|
||||
case let .setFilesFolder(filesFolder): return "/_files_folder \(filesFolder)"
|
||||
case let .apiSetXFTPConfig(cfg): if let cfg = cfg {
|
||||
return "/_xftp on \(encodeJSON(cfg))"
|
||||
} else {
|
||||
return "/_xftp off"
|
||||
}
|
||||
case let .apiSetEncryptLocalFiles(enable): return "/_files_encrypt \(onOff(enable))"
|
||||
case let .apiExportArchive(cfg): return "/_db export \(encodeJSON(cfg))"
|
||||
case let .apiImportArchive(cfg): return "/_db import \(encodeJSON(cfg))"
|
||||
@@ -178,6 +174,9 @@ public enum ChatCommand {
|
||||
let msg = encodeJSON(ComposedMessage(fileSource: file, quotedItemId: quotedItemId, msgContent: mc))
|
||||
let ttlStr = ttl != nil ? "\(ttl!)" : "default"
|
||||
return "/_send \(ref(type, id)) live=\(onOff(live)) ttl=\(ttlStr) json \(msg)"
|
||||
case let .apiCreateChatItem(noteFolderId, file, mc):
|
||||
let msg = encodeJSON(ComposedMessage(fileSource: file, msgContent: mc))
|
||||
return "/_create *\(noteFolderId) json \(msg)"
|
||||
case let .apiUpdateChatItem(type, id, itemId, mc, live): return "/_update item \(ref(type, id)) \(itemId) live=\(onOff(live)) \(mc.cmdString)"
|
||||
case let .apiDeleteChatItem(type, id, itemId, mode): return "/_delete item \(ref(type, id)) \(itemId) \(mode.rawValue)"
|
||||
case let .apiDeleteMemberChatItem(groupId, groupMemberId, itemId): return "/_delete member item #\(groupId) \(groupMemberId) \(itemId)"
|
||||
@@ -191,6 +190,7 @@ public enum ChatCommand {
|
||||
case let .apiAddMember(groupId, contactId, memberRole): return "/_add #\(groupId) \(contactId) \(memberRole)"
|
||||
case let .apiJoinGroup(groupId): return "/_join #\(groupId)"
|
||||
case let .apiMemberRole(groupId, memberId, memberRole): return "/_member role #\(groupId) \(memberId) \(memberRole.rawValue)"
|
||||
case let .apiBlockMemberForAll(groupId, memberId, blocked): return "/_block #\(groupId) \(memberId) blocked=\(onOff(blocked))"
|
||||
case let .apiRemoveMember(groupId, memberId): return "/_remove #\(groupId) \(memberId)"
|
||||
case let .apiLeaveGroup(groupId): return "/_leave #\(groupId)"
|
||||
case let .apiListMembers(groupId): return "/_members #\(groupId)"
|
||||
@@ -305,7 +305,6 @@ public enum ChatCommand {
|
||||
case .apiSuspendChat: return "apiSuspendChat"
|
||||
case .setTempFolder: return "setTempFolder"
|
||||
case .setFilesFolder: return "setFilesFolder"
|
||||
case .apiSetXFTPConfig: return "apiSetXFTPConfig"
|
||||
case .apiSetEncryptLocalFiles: return "apiSetEncryptLocalFiles"
|
||||
case .apiExportArchive: return "apiExportArchive"
|
||||
case .apiImportArchive: return "apiImportArchive"
|
||||
@@ -315,6 +314,7 @@ public enum ChatCommand {
|
||||
case .apiGetChat: return "apiGetChat"
|
||||
case .apiGetChatItemInfo: return "apiGetChatItemInfo"
|
||||
case .apiSendMessage: return "apiSendMessage"
|
||||
case .apiCreateChatItem: return "apiCreateChatItem"
|
||||
case .apiUpdateChatItem: return "apiUpdateChatItem"
|
||||
case .apiDeleteChatItem: return "apiDeleteChatItem"
|
||||
case .apiConnectContactViaAddress: return "apiConnectContactViaAddress"
|
||||
@@ -329,6 +329,7 @@ public enum ChatCommand {
|
||||
case .apiAddMember: return "apiAddMember"
|
||||
case .apiJoinGroup: return "apiJoinGroup"
|
||||
case .apiMemberRole: return "apiMemberRole"
|
||||
case .apiBlockMemberForAll: return "apiBlockMemberForAll"
|
||||
case .apiRemoveMember: return "apiRemoveMember"
|
||||
case .apiLeaveGroup: return "apiLeaveGroup"
|
||||
case .apiListMembers: return "apiListMembers"
|
||||
@@ -561,6 +562,8 @@ public enum ChatResponse: Decodable, Error {
|
||||
case joinedGroupMemberConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember, member: GroupMember)
|
||||
case memberRole(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, member: GroupMember, fromRole: GroupMemberRole, toRole: GroupMemberRole)
|
||||
case memberRoleUser(user: UserRef, groupInfo: GroupInfo, member: GroupMember, fromRole: GroupMemberRole, toRole: GroupMemberRole)
|
||||
case memberBlockedForAll(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, member: GroupMember, blocked: Bool)
|
||||
case memberBlockedForAllUser(user: UserRef, groupInfo: GroupInfo, member: GroupMember, blocked: Bool)
|
||||
case deletedMemberUser(user: UserRef, groupInfo: GroupInfo, member: GroupMember)
|
||||
case deletedMember(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, deletedMember: GroupMember)
|
||||
case leftMember(user: UserRef, groupInfo: GroupInfo, member: GroupMember)
|
||||
@@ -711,6 +714,8 @@ public enum ChatResponse: Decodable, Error {
|
||||
case .joinedGroupMemberConnecting: return "joinedGroupMemberConnecting"
|
||||
case .memberRole: return "memberRole"
|
||||
case .memberRoleUser: return "memberRoleUser"
|
||||
case .memberBlockedForAll: return "memberBlockedForAll"
|
||||
case .memberBlockedForAllUser: return "memberBlockedForAllUser"
|
||||
case .deletedMemberUser: return "deletedMemberUser"
|
||||
case .deletedMember: return "deletedMember"
|
||||
case .leftMember: return "leftMember"
|
||||
@@ -859,6 +864,8 @@ public enum ChatResponse: Decodable, Error {
|
||||
case let .joinedGroupMemberConnecting(u, groupInfo, hostMember, member): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(hostMember)\nmember: \(member)")
|
||||
case let .memberRole(u, groupInfo, byMember, member, fromRole, toRole): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\nmember: \(member)\nfromRole: \(fromRole)\ntoRole: \(toRole)")
|
||||
case let .memberRoleUser(u, groupInfo, member, fromRole, toRole): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)\nfromRole: \(fromRole)\ntoRole: \(toRole)")
|
||||
case let .memberBlockedForAll(u, groupInfo, byMember, member, blocked): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\nmember: \(member)\nblocked: \(blocked)")
|
||||
case let .memberBlockedForAllUser(u, groupInfo, member, blocked): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)\nblocked: \(blocked)")
|
||||
case let .deletedMemberUser(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)")
|
||||
case let .deletedMember(u, groupInfo, byMember, deletedMember): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\ndeletedMember: \(deletedMember)")
|
||||
case let .leftMember(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)")
|
||||
@@ -991,10 +998,6 @@ struct ComposedMessage: Encodable {
|
||||
var msgContent: MsgContent
|
||||
}
|
||||
|
||||
public struct XFTPFileConfig: Encodable {
|
||||
var minFileSize: Int64
|
||||
}
|
||||
|
||||
public struct ArchiveConfig: Encodable {
|
||||
var archivePath: String
|
||||
var disableCompression: Bool?
|
||||
@@ -1610,6 +1613,7 @@ public enum ChatErrorType: Decodable {
|
||||
case userUnknown
|
||||
case activeUserExists
|
||||
case userExists
|
||||
case invalidDisplayName
|
||||
case differentActiveUser(commandUserId: Int64, activeUserId: Int64)
|
||||
case cantDeleteActiveUser(userId: Int64)
|
||||
case cantDeleteLastUser(userId: Int64)
|
||||
|
||||
@@ -265,10 +265,6 @@ public class Default<T> {
|
||||
}
|
||||
}
|
||||
|
||||
public func getXFTPCfg() -> XFTPFileConfig {
|
||||
return XFTPFileConfig(minFileSize: 0)
|
||||
}
|
||||
|
||||
public func getNetCfg() -> NetCfg {
|
||||
let onionHosts = networkUseOnionHostsGroupDefault.get()
|
||||
let (hostMode, requiredHostMode) = onionHosts.hostMode
|
||||
|
||||
@@ -172,7 +172,6 @@ public func fromLocalProfile (_ profile: LocalProfile) -> Profile {
|
||||
}
|
||||
|
||||
public struct UserProfileUpdateSummary: Decodable {
|
||||
public var notChanged: Int
|
||||
public var updateSuccesses: Int
|
||||
public var updateFailures: Int
|
||||
public var changedContacts: [Contact]
|
||||
@@ -181,6 +180,7 @@ public struct UserProfileUpdateSummary: Decodable {
|
||||
public enum ChatType: String {
|
||||
case direct = "@"
|
||||
case group = "#"
|
||||
case local = "*"
|
||||
case contactRequest = "<@"
|
||||
case contactConnection = ":"
|
||||
}
|
||||
@@ -616,8 +616,8 @@ public enum ChatFeature: String, Decodable, Feature {
|
||||
}
|
||||
case .fullDelete:
|
||||
switch allowed {
|
||||
case .always: return "Allow your contacts to irreversibly delete sent messages."
|
||||
case .yes: return "Allow irreversible message deletion only if your contact allows it to you."
|
||||
case .always: return "Allow your contacts to irreversibly delete sent messages. (24 hours)"
|
||||
case .yes: return "Allow irreversible message deletion only if your contact allows it to you. (24 hours)"
|
||||
case .no: return "Contacts can mark messages for deletion; you will be able to view them."
|
||||
}
|
||||
case .reactions:
|
||||
@@ -653,11 +653,11 @@ public enum ChatFeature: String, Decodable, Feature {
|
||||
: "Disappearing messages are prohibited in this chat."
|
||||
case .fullDelete:
|
||||
return enabled.forUser && enabled.forContact
|
||||
? "Both you and your contact can irreversibly delete sent messages."
|
||||
? "Both you and your contact can irreversibly delete sent messages. (24 hours)"
|
||||
: enabled.forUser
|
||||
? "Only you can irreversibly delete messages (your contact can mark them for deletion)."
|
||||
? "Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)"
|
||||
: enabled.forContact
|
||||
? "Only your contact can irreversibly delete messages (you can mark them for deletion)."
|
||||
? "Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours)"
|
||||
: "Irreversible message deletion is prohibited in this chat."
|
||||
case .reactions:
|
||||
return enabled.forUser && enabled.forContact
|
||||
@@ -694,6 +694,7 @@ public enum GroupFeature: String, Decodable, Feature {
|
||||
case reactions
|
||||
case voice
|
||||
case files
|
||||
case history
|
||||
|
||||
public var id: Self { self }
|
||||
|
||||
@@ -712,6 +713,7 @@ public enum GroupFeature: String, Decodable, Feature {
|
||||
case .reactions: return NSLocalizedString("Message reactions", comment: "chat feature")
|
||||
case .voice: return NSLocalizedString("Voice messages", comment: "chat feature")
|
||||
case .files: return NSLocalizedString("Files and media", comment: "chat feature")
|
||||
case .history: return NSLocalizedString("Visible history", comment: "chat feature")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -723,6 +725,7 @@ public enum GroupFeature: String, Decodable, Feature {
|
||||
case .reactions: return "face.smiling"
|
||||
case .voice: return "mic"
|
||||
case .files: return "doc"
|
||||
case .history: return "clock"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -734,6 +737,7 @@ public enum GroupFeature: String, Decodable, Feature {
|
||||
case .reactions: return "face.smiling.fill"
|
||||
case .voice: return "mic.fill"
|
||||
case .files: return "doc.fill"
|
||||
case .history: return "clock.fill"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -759,7 +763,7 @@ public enum GroupFeature: String, Decodable, Feature {
|
||||
}
|
||||
case .fullDelete:
|
||||
switch enabled {
|
||||
case .on: return "Allow to irreversibly delete sent messages."
|
||||
case .on: return "Allow to irreversibly delete sent messages. (24 hours)"
|
||||
case .off: return "Prohibit irreversible message deletion."
|
||||
}
|
||||
case .reactions:
|
||||
@@ -777,6 +781,11 @@ public enum GroupFeature: String, Decodable, Feature {
|
||||
case .on: return "Allow to send files and media."
|
||||
case .off: return "Prohibit sending files and media."
|
||||
}
|
||||
case .history:
|
||||
switch enabled {
|
||||
case .on: return "Send up to 100 last messages to new members."
|
||||
case .off: return "Do not send history to new members."
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch self {
|
||||
@@ -792,7 +801,7 @@ public enum GroupFeature: String, Decodable, Feature {
|
||||
}
|
||||
case .fullDelete:
|
||||
switch enabled {
|
||||
case .on: return "Group members can irreversibly delete sent messages."
|
||||
case .on: return "Group members can irreversibly delete sent messages. (24 hours)"
|
||||
case .off: return "Irreversible message deletion is prohibited in this group."
|
||||
}
|
||||
case .reactions:
|
||||
@@ -810,6 +819,11 @@ public enum GroupFeature: String, Decodable, Feature {
|
||||
case .on: return "Group members can send files and media."
|
||||
case .off: return "Files and media are prohibited in this group."
|
||||
}
|
||||
case .history:
|
||||
switch enabled {
|
||||
case .on: return "Up to 100 last messages are sent to new members."
|
||||
case .off: return "History is not sent to new members."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -949,6 +963,7 @@ public struct FullGroupPreferences: Decodable, Equatable {
|
||||
public var reactions: GroupPreference
|
||||
public var voice: GroupPreference
|
||||
public var files: GroupPreference
|
||||
public var history: GroupPreference
|
||||
|
||||
public init(
|
||||
timedMessages: TimedMessagesGroupPreference,
|
||||
@@ -956,7 +971,8 @@ public struct FullGroupPreferences: Decodable, Equatable {
|
||||
fullDelete: GroupPreference,
|
||||
reactions: GroupPreference,
|
||||
voice: GroupPreference,
|
||||
files: GroupPreference
|
||||
files: GroupPreference,
|
||||
history: GroupPreference
|
||||
) {
|
||||
self.timedMessages = timedMessages
|
||||
self.directMessages = directMessages
|
||||
@@ -964,6 +980,7 @@ public struct FullGroupPreferences: Decodable, Equatable {
|
||||
self.reactions = reactions
|
||||
self.voice = voice
|
||||
self.files = files
|
||||
self.history = history
|
||||
}
|
||||
|
||||
public static let sampleData = FullGroupPreferences(
|
||||
@@ -972,7 +989,8 @@ public struct FullGroupPreferences: Decodable, Equatable {
|
||||
fullDelete: GroupPreference(enable: .off),
|
||||
reactions: GroupPreference(enable: .on),
|
||||
voice: GroupPreference(enable: .on),
|
||||
files: GroupPreference(enable: .on)
|
||||
files: GroupPreference(enable: .on),
|
||||
history: GroupPreference(enable: .on)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -983,14 +1001,16 @@ public struct GroupPreferences: Codable {
|
||||
public var reactions: GroupPreference?
|
||||
public var voice: GroupPreference?
|
||||
public var files: GroupPreference?
|
||||
public var history: GroupPreference?
|
||||
|
||||
public init(
|
||||
timedMessages: TimedMessagesGroupPreference?,
|
||||
directMessages: GroupPreference?,
|
||||
fullDelete: GroupPreference?,
|
||||
reactions: GroupPreference?,
|
||||
voice: GroupPreference?,
|
||||
files: GroupPreference?
|
||||
timedMessages: TimedMessagesGroupPreference? = nil,
|
||||
directMessages: GroupPreference? = nil,
|
||||
fullDelete: GroupPreference? = nil,
|
||||
reactions: GroupPreference? = nil,
|
||||
voice: GroupPreference? = nil,
|
||||
files: GroupPreference? = nil,
|
||||
history: GroupPreference? = nil
|
||||
) {
|
||||
self.timedMessages = timedMessages
|
||||
self.directMessages = directMessages
|
||||
@@ -998,6 +1018,7 @@ public struct GroupPreferences: Codable {
|
||||
self.reactions = reactions
|
||||
self.voice = voice
|
||||
self.files = files
|
||||
self.history = history
|
||||
}
|
||||
|
||||
public static let sampleData = GroupPreferences(
|
||||
@@ -1006,7 +1027,8 @@ public struct GroupPreferences: Codable {
|
||||
fullDelete: GroupPreference(enable: .off),
|
||||
reactions: GroupPreference(enable: .on),
|
||||
voice: GroupPreference(enable: .on),
|
||||
files: GroupPreference(enable: .on)
|
||||
files: GroupPreference(enable: .on),
|
||||
history: GroupPreference(enable: .on)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1017,7 +1039,8 @@ public func toGroupPreferences(_ fullPreferences: FullGroupPreferences) -> Group
|
||||
fullDelete: fullPreferences.fullDelete,
|
||||
reactions: fullPreferences.reactions,
|
||||
voice: fullPreferences.voice,
|
||||
files: fullPreferences.files
|
||||
files: fullPreferences.files,
|
||||
history: fullPreferences.history
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1073,17 +1096,21 @@ public enum GroupFeatureEnabled: String, Codable, Identifiable {
|
||||
public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
case direct(contact: Contact)
|
||||
case group(groupInfo: GroupInfo)
|
||||
case local(noteFolder: NoteFolder)
|
||||
case contactRequest(contactRequest: UserContactRequest)
|
||||
case contactConnection(contactConnection: PendingContactConnection)
|
||||
case invalidJSON(json: String)
|
||||
|
||||
private static let invalidChatName = NSLocalizedString("invalid chat", comment: "invalid chat data")
|
||||
|
||||
static let privateNotesChatName = NSLocalizedString("Private notes", comment: "name of notes to self")
|
||||
|
||||
public var localDisplayName: String {
|
||||
get {
|
||||
switch self {
|
||||
case let .direct(contact): return contact.localDisplayName
|
||||
case let .group(groupInfo): return groupInfo.localDisplayName
|
||||
case .local: return ""
|
||||
case let .contactRequest(contactRequest): return contactRequest.localDisplayName
|
||||
case let .contactConnection(contactConnection): return contactConnection.localDisplayName
|
||||
case .invalidJSON: return ChatInfo.invalidChatName
|
||||
@@ -1096,6 +1123,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
switch self {
|
||||
case let .direct(contact): return contact.displayName
|
||||
case let .group(groupInfo): return groupInfo.displayName
|
||||
case .local: return ChatInfo.privateNotesChatName
|
||||
case let .contactRequest(contactRequest): return contactRequest.displayName
|
||||
case let .contactConnection(contactConnection): return contactConnection.displayName
|
||||
case .invalidJSON: return ChatInfo.invalidChatName
|
||||
@@ -1108,6 +1136,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
switch self {
|
||||
case let .direct(contact): return contact.fullName
|
||||
case let .group(groupInfo): return groupInfo.fullName
|
||||
case .local: return ""
|
||||
case let .contactRequest(contactRequest): return contactRequest.fullName
|
||||
case let .contactConnection(contactConnection): return contactConnection.fullName
|
||||
case .invalidJSON: return ChatInfo.invalidChatName
|
||||
@@ -1120,6 +1149,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
switch self {
|
||||
case let .direct(contact): return contact.image
|
||||
case let .group(groupInfo): return groupInfo.image
|
||||
case .local: return nil
|
||||
case let .contactRequest(contactRequest): return contactRequest.image
|
||||
case let .contactConnection(contactConnection): return contactConnection.image
|
||||
case .invalidJSON: return nil
|
||||
@@ -1132,6 +1162,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
switch self {
|
||||
case let .direct(contact): return contact.localAlias
|
||||
case let .group(groupInfo): return groupInfo.localAlias
|
||||
case .local: return ""
|
||||
case let .contactRequest(contactRequest): return contactRequest.localAlias
|
||||
case let .contactConnection(contactConnection): return contactConnection.localAlias
|
||||
case .invalidJSON: return ""
|
||||
@@ -1144,6 +1175,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
switch self {
|
||||
case let .direct(contact): return contact.id
|
||||
case let .group(groupInfo): return groupInfo.id
|
||||
case let .local(noteFolder): return noteFolder.id
|
||||
case let .contactRequest(contactRequest): return contactRequest.id
|
||||
case let .contactConnection(contactConnection): return contactConnection.id
|
||||
case .invalidJSON: return ""
|
||||
@@ -1156,6 +1188,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
switch self {
|
||||
case .direct: return .direct
|
||||
case .group: return .group
|
||||
case .local: return .local
|
||||
case .contactRequest: return .contactRequest
|
||||
case .contactConnection: return .contactConnection
|
||||
case .invalidJSON: return .direct
|
||||
@@ -1168,6 +1201,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
switch self {
|
||||
case let .direct(contact): return contact.apiId
|
||||
case let .group(groupInfo): return groupInfo.apiId
|
||||
case let .local(noteFolder): return noteFolder.apiId
|
||||
case let .contactRequest(contactRequest): return contactRequest.apiId
|
||||
case let .contactConnection(contactConnection): return contactConnection.apiId
|
||||
case .invalidJSON: return 0
|
||||
@@ -1180,6 +1214,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
switch self {
|
||||
case let .direct(contact): return contact.ready
|
||||
case let .group(groupInfo): return groupInfo.ready
|
||||
case let .local(noteFolder): return noteFolder.ready
|
||||
case let .contactRequest(contactRequest): return contactRequest.ready
|
||||
case let .contactConnection(contactConnection): return contactConnection.ready
|
||||
case .invalidJSON: return false
|
||||
@@ -1192,6 +1227,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
switch self {
|
||||
case let .direct(contact): return contact.sendMsgEnabled
|
||||
case let .group(groupInfo): return groupInfo.sendMsgEnabled
|
||||
case let .local(noteFolder): return noteFolder.sendMsgEnabled
|
||||
case let .contactRequest(contactRequest): return contactRequest.sendMsgEnabled
|
||||
case let .contactConnection(contactConnection): return contactConnection.sendMsgEnabled
|
||||
case .invalidJSON: return false
|
||||
@@ -1204,6 +1240,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
switch self {
|
||||
case let .direct(contact): return contact.contactConnIncognito
|
||||
case let .group(groupInfo): return groupInfo.membership.memberIncognito
|
||||
case .local: return false
|
||||
case .contactRequest: return false
|
||||
case let .contactConnection(contactConnection): return contactConnection.incognito
|
||||
case .invalidJSON: return false
|
||||
@@ -1246,6 +1283,11 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
case .voice: return prefs.voice.on
|
||||
case .calls: return false
|
||||
}
|
||||
case .local:
|
||||
switch feature {
|
||||
case .voice: return true
|
||||
default: return false
|
||||
}
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
@@ -1307,6 +1349,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
switch self {
|
||||
case let .direct(contact): return contact.createdAt
|
||||
case let .group(groupInfo): return groupInfo.createdAt
|
||||
case let .local(noteFolder): return noteFolder.createdAt
|
||||
case let .contactRequest(contactRequest): return contactRequest.createdAt
|
||||
case let .contactConnection(contactConnection): return contactConnection.createdAt
|
||||
case .invalidJSON: return .now
|
||||
@@ -1317,6 +1360,18 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
switch self {
|
||||
case let .direct(contact): return contact.updatedAt
|
||||
case let .group(groupInfo): return groupInfo.updatedAt
|
||||
case let .local(noteFolder): return noteFolder.updatedAt
|
||||
case let .contactRequest(contactRequest): return contactRequest.updatedAt
|
||||
case let .contactConnection(contactConnection): return contactConnection.updatedAt
|
||||
case .invalidJSON: return .now
|
||||
}
|
||||
}
|
||||
|
||||
public var chatTs: Date {
|
||||
switch self {
|
||||
case let .direct(contact): return contact.chatTs ?? contact.updatedAt
|
||||
case let .group(groupInfo): return groupInfo.chatTs ?? groupInfo.updatedAt
|
||||
case let .local(noteFolder): return noteFolder.chatTs
|
||||
case let .contactRequest(contactRequest): return contactRequest.updatedAt
|
||||
case let .contactConnection(contactConnection): return contactConnection.updatedAt
|
||||
case .invalidJSON: return .now
|
||||
@@ -1326,6 +1381,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
public struct SampleData {
|
||||
public var direct: ChatInfo
|
||||
public var group: ChatInfo
|
||||
public var local: ChatInfo
|
||||
public var contactRequest: ChatInfo
|
||||
public var contactConnection: ChatInfo
|
||||
}
|
||||
@@ -1333,6 +1389,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
public static var sampleData: ChatInfo.SampleData = SampleData(
|
||||
direct: ChatInfo.direct(contact: Contact.sampleData),
|
||||
group: ChatInfo.group(groupInfo: GroupInfo.sampleData),
|
||||
local: ChatInfo.local(noteFolder: NoteFolder.sampleData),
|
||||
contactRequest: ChatInfo.contactRequest(contactRequest: UserContactRequest.sampleData),
|
||||
contactConnection: ChatInfo.contactConnection(contactConnection: PendingContactConnection.getSampleData())
|
||||
)
|
||||
@@ -1379,6 +1436,7 @@ public struct Contact: Identifiable, Decodable, NamedChat {
|
||||
public var mergedPreferences: ContactUserPreferences
|
||||
var createdAt: Date
|
||||
var updatedAt: Date
|
||||
var chatTs: Date?
|
||||
var contactGroupMemberId: Int64?
|
||||
var contactGrpInvSent: Bool
|
||||
|
||||
@@ -1698,6 +1756,7 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat {
|
||||
public var chatSettings: ChatSettings
|
||||
var createdAt: Date
|
||||
var updatedAt: Date
|
||||
var chatTs: Date?
|
||||
|
||||
public var id: ChatId { get { "#\(groupId)" } }
|
||||
public var apiId: Int64 { get { groupId } }
|
||||
@@ -1768,6 +1827,7 @@ public struct GroupMember: Identifiable, Decodable {
|
||||
public var memberCategory: GroupMemberCategory
|
||||
public var memberStatus: GroupMemberStatus
|
||||
public var memberSettings: GroupMemberSettings
|
||||
public var blockedByAdmin: Bool
|
||||
public var invitedBy: InvitedBy
|
||||
public var localDisplayName: ContactName
|
||||
public var memberProfile: LocalProfile
|
||||
@@ -1779,13 +1839,15 @@ public struct GroupMember: Identifiable, Decodable {
|
||||
public var displayName: String {
|
||||
get {
|
||||
let p = memberProfile
|
||||
return p.localAlias == "" ? p.displayName : p.localAlias
|
||||
let name = p.localAlias == "" ? p.displayName : p.localAlias
|
||||
return pastMember(name)
|
||||
}
|
||||
}
|
||||
public var fullName: String { get { memberProfile.fullName } }
|
||||
public var image: String? { get { memberProfile.image } }
|
||||
public var contactLink: String? { get { memberProfile.contactLink } }
|
||||
public var verified: Bool { activeConn?.connectionCode != nil }
|
||||
public var blocked: Bool { blockedByAdmin || !memberSettings.showMessages }
|
||||
|
||||
var directChatId: ChatId? {
|
||||
get {
|
||||
@@ -1800,17 +1862,27 @@ public struct GroupMember: Identifiable, Decodable {
|
||||
public var chatViewName: String {
|
||||
get {
|
||||
let p = memberProfile
|
||||
return p.localAlias == ""
|
||||
? p.displayName + (p.fullName == "" || p.fullName == p.displayName ? "" : " / \(p.fullName)")
|
||||
: p.localAlias
|
||||
let name = (
|
||||
p.localAlias == ""
|
||||
? p.displayName + (p.fullName == "" || p.fullName == p.displayName ? "" : " / \(p.fullName)")
|
||||
: p.localAlias
|
||||
)
|
||||
return pastMember(name)
|
||||
}
|
||||
}
|
||||
|
||||
private func pastMember(_ name: String) -> String {
|
||||
memberStatus == .memUnknown
|
||||
? String.localizedStringWithFormat(NSLocalizedString("Past member %@", comment: "past/unknown group member"), name)
|
||||
: name
|
||||
}
|
||||
|
||||
public var memberActive: Bool {
|
||||
switch memberStatus {
|
||||
case .memRemoved: return false
|
||||
case .memLeft: return false
|
||||
case .memGroupDeleted: return false
|
||||
case .memUnknown: return false
|
||||
case .memInvited: return false
|
||||
case .memIntroduced: return false
|
||||
case .memIntroInvited: return false
|
||||
@@ -1827,6 +1899,7 @@ public struct GroupMember: Identifiable, Decodable {
|
||||
case .memRemoved: return false
|
||||
case .memLeft: return false
|
||||
case .memGroupDeleted: return false
|
||||
case .memUnknown: return false
|
||||
case .memInvited: return false
|
||||
case .memIntroduced: return true
|
||||
case .memIntroInvited: return true
|
||||
@@ -1841,7 +1914,7 @@ public struct GroupMember: Identifiable, Decodable {
|
||||
public func canBeRemoved(groupInfo: GroupInfo) -> Bool {
|
||||
let userRole = groupInfo.membership.memberRole
|
||||
return memberStatus != .memRemoved && memberStatus != .memLeft
|
||||
&& userRole >= .admin && userRole >= memberRole && groupInfo.membership.memberCurrent
|
||||
&& userRole >= .admin && userRole >= memberRole && groupInfo.membership.memberActive
|
||||
}
|
||||
|
||||
public func canChangeRoleTo(groupInfo: GroupInfo) -> [GroupMemberRole]? {
|
||||
@@ -1850,6 +1923,12 @@ public struct GroupMember: Identifiable, Decodable {
|
||||
return GroupMemberRole.allCases.filter { $0 <= userRole && $0 != .author }
|
||||
}
|
||||
|
||||
public func canBlockForAll(groupInfo: GroupInfo) -> Bool {
|
||||
let userRole = groupInfo.membership.memberRole
|
||||
return memberStatus != .memRemoved && memberStatus != .memLeft && memberRole < .admin
|
||||
&& userRole >= .admin && userRole >= memberRole && groupInfo.membership.memberActive
|
||||
}
|
||||
|
||||
public var memberIncognito: Bool {
|
||||
memberProfile.profileId != memberContactProfileId
|
||||
}
|
||||
@@ -1862,6 +1941,7 @@ public struct GroupMember: Identifiable, Decodable {
|
||||
memberCategory: .inviteeMember,
|
||||
memberStatus: .memComplete,
|
||||
memberSettings: GroupMemberSettings(showMessages: true),
|
||||
blockedByAdmin: false,
|
||||
invitedBy: .user,
|
||||
localDisplayName: "alice",
|
||||
memberProfile: LocalProfile.sampleData,
|
||||
@@ -1931,6 +2011,7 @@ public enum GroupMemberStatus: String, Decodable {
|
||||
case memRemoved = "removed"
|
||||
case memLeft = "left"
|
||||
case memGroupDeleted = "deleted"
|
||||
case memUnknown = "unknown"
|
||||
case memInvited = "invited"
|
||||
case memIntroduced = "introduced"
|
||||
case memIntroInvited = "intro-inv"
|
||||
@@ -1945,6 +2026,7 @@ public enum GroupMemberStatus: String, Decodable {
|
||||
case .memRemoved: return "removed"
|
||||
case .memLeft: return "left"
|
||||
case .memGroupDeleted: return "group deleted"
|
||||
case .memUnknown: return "unknown status"
|
||||
case .memInvited: return "invited"
|
||||
case .memIntroduced: return "connecting (introduced)"
|
||||
case .memIntroInvited: return "connecting (introduction invitation)"
|
||||
@@ -1961,6 +2043,7 @@ public enum GroupMemberStatus: String, Decodable {
|
||||
case .memRemoved: return "removed"
|
||||
case .memLeft: return "left"
|
||||
case .memGroupDeleted: return "group deleted"
|
||||
case .memUnknown: return "unknown"
|
||||
case .memInvited: return "invited"
|
||||
case .memIntroduced: return "connecting"
|
||||
case .memIntroInvited: return "connecting"
|
||||
@@ -1973,6 +2056,39 @@ public enum GroupMemberStatus: String, Decodable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct NoteFolder: Identifiable, Decodable, NamedChat {
|
||||
public var noteFolderId: Int64
|
||||
public var favorite: Bool
|
||||
public var unread: Bool
|
||||
var createdAt: Date
|
||||
public var updatedAt: Date
|
||||
var chatTs: Date
|
||||
|
||||
public var id: ChatId { get { "*\(noteFolderId)" } }
|
||||
public var apiId: Int64 { get { noteFolderId } }
|
||||
public var ready: Bool { get { true } }
|
||||
public var sendMsgEnabled: Bool { get { true } }
|
||||
public var displayName: String { get { ChatInfo.privateNotesChatName } }
|
||||
public var fullName: String { get { "" } }
|
||||
public var image: String? { get { nil } }
|
||||
public var localAlias: String { get { "" } }
|
||||
|
||||
public var canEdit: Bool { true }
|
||||
|
||||
public var canDelete: Bool { true }
|
||||
|
||||
public var canAddMembers: Bool { false }
|
||||
|
||||
public static let sampleData = NoteFolder(
|
||||
noteFolderId: 1,
|
||||
favorite: false,
|
||||
unread: false,
|
||||
createdAt: .now,
|
||||
updatedAt: .now,
|
||||
chatTs: .now
|
||||
)
|
||||
}
|
||||
|
||||
public enum InvitedBy: Decodable {
|
||||
case contact(byContactId: Int64)
|
||||
case user
|
||||
@@ -2092,6 +2208,7 @@ public struct ChatItem: Identifiable, Decodable {
|
||||
case .rcvDeleted: return true
|
||||
case .sndModerated: return true
|
||||
case .rcvModerated: return true
|
||||
case .rcvBlocked: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
@@ -2136,50 +2253,53 @@ public struct ChatItem: Identifiable, Decodable {
|
||||
}
|
||||
}
|
||||
|
||||
private var showNtfDir: Bool {
|
||||
return !chatDir.sent
|
||||
}
|
||||
|
||||
public var showNotification: Bool {
|
||||
switch content {
|
||||
case .sndMsgContent: return showNtfDir
|
||||
case .rcvMsgContent: return showNtfDir
|
||||
case .sndDeleted: return showNtfDir
|
||||
case .rcvDeleted: return showNtfDir
|
||||
case .sndCall: return showNtfDir
|
||||
case .sndMsgContent: return false
|
||||
case .rcvMsgContent: return meta.itemDeleted == nil
|
||||
case .sndDeleted: return false
|
||||
case .rcvDeleted: return false
|
||||
case .sndCall: return false
|
||||
case .rcvCall: return false // notification is shown on .callInvitation instead
|
||||
case .rcvIntegrityError: return showNtfDir
|
||||
case .rcvDecryptionError: return showNtfDir
|
||||
case .rcvGroupInvitation: return showNtfDir
|
||||
case .sndGroupInvitation: return showNtfDir
|
||||
case .rcvDirectEvent: return false
|
||||
case .rcvIntegrityError: return false
|
||||
case .rcvDecryptionError: return false
|
||||
case .rcvGroupInvitation: return true
|
||||
case .sndGroupInvitation: return false
|
||||
case .rcvDirectEvent(rcvDirectEvent: let rcvDirectEvent):
|
||||
switch rcvDirectEvent {
|
||||
case .contactDeleted: return false
|
||||
case .profileUpdated: return true
|
||||
}
|
||||
case .rcvGroupEvent(rcvGroupEvent: let rcvGroupEvent):
|
||||
switch rcvGroupEvent {
|
||||
case .groupUpdated: return false
|
||||
case .memberConnected: return false
|
||||
case .memberRole: return false
|
||||
case .userRole: return showNtfDir
|
||||
case .userDeleted: return showNtfDir
|
||||
case .groupDeleted: return showNtfDir
|
||||
case .memberBlocked: return false
|
||||
case .userRole: return true
|
||||
case .userDeleted: return true
|
||||
case .groupDeleted: return true
|
||||
case .memberAdded: return false
|
||||
case .memberLeft: return false
|
||||
case .memberDeleted: return false
|
||||
case .invitedViaGroupLink: return false
|
||||
case .memberCreatedContact: return false
|
||||
case .memberProfileUpdated: return false
|
||||
}
|
||||
case .sndGroupEvent: return showNtfDir
|
||||
case .sndGroupEvent: return false
|
||||
case .rcvConnEvent: return false
|
||||
case .sndConnEvent: return showNtfDir
|
||||
case .sndConnEvent: return false
|
||||
case .rcvChatFeature: return false
|
||||
case .sndChatFeature: return showNtfDir
|
||||
case .sndChatFeature: return false
|
||||
case .rcvChatPreference: return false
|
||||
case .sndChatPreference: return showNtfDir
|
||||
case .sndChatPreference: return false
|
||||
case .rcvGroupFeature: return false
|
||||
case .sndGroupFeature: return showNtfDir
|
||||
case .rcvChatFeatureRejected: return showNtfDir
|
||||
case .rcvGroupFeatureRejected: return showNtfDir
|
||||
case .sndModerated: return true
|
||||
case .rcvModerated: return true
|
||||
case .sndGroupFeature: return false
|
||||
case .rcvChatFeatureRejected: return true
|
||||
case .rcvGroupFeatureRejected: return false
|
||||
case .sndModerated: return false
|
||||
case .rcvModerated: return false
|
||||
case .rcvBlocked: return false
|
||||
case .invalidJSON: return false
|
||||
}
|
||||
}
|
||||
@@ -2205,18 +2325,25 @@ public struct ChatItem: Identifiable, Decodable {
|
||||
return fileSource.cryptoArgs != nil
|
||||
}
|
||||
|
||||
public var encryptLocalFile: Bool {
|
||||
content.msgContent?.isVideo == false &&
|
||||
privacyEncryptLocalFilesGroupDefault.get()
|
||||
public var memberDisplayName: String? {
|
||||
if case let .groupRcv(groupMember) = chatDir {
|
||||
switch content {
|
||||
case let .rcvGroupEvent(rcvGroupEvent: .memberProfileUpdated(fromProfile, toProfile)):
|
||||
toProfile.displayName != fromProfile.displayName || toProfile.fullName != fromProfile.fullName
|
||||
? nil
|
||||
: groupMember.chatViewName
|
||||
default:
|
||||
groupMember.chatViewName
|
||||
}
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
public var memberDisplayName: String? {
|
||||
get {
|
||||
if case let .groupRcv(groupMember) = chatDir {
|
||||
return groupMember.chatViewName
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
public var localNote: Bool {
|
||||
switch chatDir {
|
||||
case .localSnd, .localRcv: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2381,6 +2508,8 @@ public enum CIDirection: Decodable {
|
||||
case directRcv
|
||||
case groupSnd
|
||||
case groupRcv(groupMember: GroupMember)
|
||||
case localSnd
|
||||
case localRcv
|
||||
|
||||
public var sent: Bool {
|
||||
get {
|
||||
@@ -2389,6 +2518,8 @@ public enum CIDirection: Decodable {
|
||||
case .directRcv: return false
|
||||
case .groupSnd: return true
|
||||
case .groupRcv: return false
|
||||
case .localSnd: return true
|
||||
case .localRcv: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2555,12 +2686,14 @@ public enum SndCIStatusProgress: String, Decodable {
|
||||
public enum CIDeleted: Decodable {
|
||||
case deleted(deletedTs: Date?)
|
||||
case blocked(deletedTs: Date?)
|
||||
case blockedByAdmin(deletedTs: Date?)
|
||||
case moderated(deletedTs: Date?, byGroupMember: GroupMember)
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case .deleted: return "deleted"
|
||||
case .blocked: return "blocked"
|
||||
case .blockedByAdmin: return "blocked by admin"
|
||||
case .moderated: return "moderated"
|
||||
}
|
||||
}
|
||||
@@ -2601,6 +2734,7 @@ public enum CIContent: Decodable, ItemContent {
|
||||
case rcvGroupFeatureRejected(groupFeature: GroupFeature)
|
||||
case sndModerated
|
||||
case rcvModerated
|
||||
case rcvBlocked
|
||||
case invalidJSON(json: String)
|
||||
|
||||
public var text: String {
|
||||
@@ -2631,6 +2765,7 @@ public enum CIContent: Decodable, ItemContent {
|
||||
case let .rcvGroupFeatureRejected(groupFeature): return String.localizedStringWithFormat("%@: received, prohibited", groupFeature.text)
|
||||
case .sndModerated: return NSLocalizedString("moderated", comment: "moderated chat item")
|
||||
case .rcvModerated: return NSLocalizedString("moderated", comment: "moderated chat item")
|
||||
case .rcvBlocked: return NSLocalizedString("blocked by admin", comment: "blocked chat item")
|
||||
case .invalidJSON: return NSLocalizedString("invalid data", comment: "invalid chat item")
|
||||
}
|
||||
}
|
||||
@@ -2669,6 +2804,7 @@ public enum CIContent: Decodable, ItemContent {
|
||||
case .rcvDecryptionError: return true
|
||||
case .rcvGroupInvitation: return true
|
||||
case .rcvModerated: return true
|
||||
case .rcvBlocked: return true
|
||||
case .invalidJSON: return true
|
||||
default: return false
|
||||
}
|
||||
@@ -2714,6 +2850,8 @@ public struct CIQuote: Decodable, ItemContent {
|
||||
case .directRcv: return nil
|
||||
case .groupSnd: return membership?.displayName ?? "you"
|
||||
case let .groupRcv(member): return member.displayName
|
||||
case .localSnd: return "you"
|
||||
case .localRcv: return nil
|
||||
case nil: return nil
|
||||
}
|
||||
}
|
||||
@@ -2873,6 +3011,39 @@ public struct CryptoFile: Codable {
|
||||
public static func plain(_ f: String) -> CryptoFile {
|
||||
CryptoFile(filePath: f, cryptoArgs: nil)
|
||||
}
|
||||
|
||||
private func decryptToTmpFile(_ filesToDelete: inout Set<URL>) async -> URL? {
|
||||
if let cfArgs = cryptoArgs {
|
||||
let url = getAppFilePath(filePath)
|
||||
let tempUrl = getTempFilesDirectory().appendingPathComponent(filePath)
|
||||
_ = filesToDelete.insert(tempUrl)
|
||||
do {
|
||||
try decryptCryptoFile(fromPath: url.path, cryptoArgs: cfArgs, toPath: tempUrl.path)
|
||||
return tempUrl
|
||||
} catch {
|
||||
logger.error("Error decrypting file: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func decryptedGet() -> URL? {
|
||||
let decrypted = CryptoFile.decryptedUrls[filePath]
|
||||
return if let decrypted = decrypted, FileManager.default.fileExists(atPath: decrypted.path) { decrypted } else { nil }
|
||||
}
|
||||
|
||||
public func decryptedGetOrCreate(_ filesToDelete: inout Set<URL>) async -> URL? {
|
||||
if let decrypted = decryptedGet() {
|
||||
return decrypted
|
||||
} else if let decrypted = await decryptToTmpFile(&filesToDelete) {
|
||||
CryptoFile.decryptedUrls[filePath] = decrypted
|
||||
return decrypted
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
static var decryptedUrls = Dictionary<String, URL>()
|
||||
}
|
||||
|
||||
public struct CryptoFileArgs: Codable {
|
||||
@@ -2921,6 +3092,7 @@ private var rcvCancelAction = CancelAction(
|
||||
public enum FileProtocol: String, Decodable {
|
||||
case smp = "smp"
|
||||
case xftp = "xftp"
|
||||
case local = "local"
|
||||
}
|
||||
|
||||
public enum CIFileStatus: Decodable, Equatable {
|
||||
@@ -3108,6 +3280,10 @@ extension MsgContent: Encodable {
|
||||
public struct FormattedText: Decodable {
|
||||
public var text: String
|
||||
public var format: Format?
|
||||
|
||||
public var isSecret: Bool {
|
||||
if case .secret = format { true } else { false }
|
||||
}
|
||||
}
|
||||
|
||||
public enum Format: Decodable, Equatable {
|
||||
@@ -3121,6 +3297,15 @@ public enum Format: Decodable, Equatable {
|
||||
case simplexLink(linkType: SimplexLinkType, simplexUri: String, smpHosts: [String])
|
||||
case email
|
||||
case phone
|
||||
|
||||
public var isSimplexLink: Bool {
|
||||
get {
|
||||
switch (self) {
|
||||
case .simplexLink: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum SimplexLinkType: String, Decodable {
|
||||
@@ -3274,10 +3459,29 @@ public enum CIGroupInvitationStatus: String, Decodable {
|
||||
|
||||
public enum RcvDirectEvent: Decodable {
|
||||
case contactDeleted
|
||||
case profileUpdated(fromProfile: Profile, toProfile: Profile)
|
||||
|
||||
var text: String {
|
||||
switch self {
|
||||
case .contactDeleted: return NSLocalizedString("deleted contact", comment: "rcv direct event chat item")
|
||||
case let .profileUpdated(fromProfile, toProfile): return profileUpdatedText(fromProfile, toProfile)
|
||||
}
|
||||
}
|
||||
|
||||
private func profileUpdatedText(_ from: Profile, _ to: Profile) -> String {
|
||||
if to.displayName != from.displayName || to.fullName != from.fullName {
|
||||
String.localizedStringWithFormat(NSLocalizedString("contact %@ changed to %@", comment: "profile update event chat item"), from.profileViewName, to.profileViewName)
|
||||
} else if to.image != from.image {
|
||||
to.image == nil
|
||||
? NSLocalizedString("removed profile picture", comment: "profile update event chat item")
|
||||
: NSLocalizedString("set new profile picture", comment: "profile update event chat item")
|
||||
} else if to.contactLink != from.contactLink {
|
||||
to.contactLink == nil
|
||||
? NSLocalizedString("removed contact address", comment: "profile update event chat item")
|
||||
: NSLocalizedString("set new contact address", comment: "profile update event chat item")
|
||||
} else {
|
||||
// shouldn't happen if backend correctly creates item; UI should be synchronized with backend
|
||||
NSLocalizedString("updated profile", comment: "profile update event chat item")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3287,6 +3491,7 @@ public enum RcvGroupEvent: Decodable {
|
||||
case memberConnected
|
||||
case memberLeft
|
||||
case memberRole(groupMemberId: Int64, profile: Profile, role: GroupMemberRole)
|
||||
case memberBlocked(groupMemberId: Int64, profile: Profile, blocked: Bool)
|
||||
case userRole(role: GroupMemberRole)
|
||||
case memberDeleted(groupMemberId: Int64, profile: Profile)
|
||||
case userDeleted
|
||||
@@ -3294,6 +3499,7 @@ public enum RcvGroupEvent: Decodable {
|
||||
case groupUpdated(groupProfile: GroupProfile)
|
||||
case invitedViaGroupLink
|
||||
case memberCreatedContact
|
||||
case memberProfileUpdated(fromProfile: Profile, toProfile: Profile)
|
||||
|
||||
var text: String {
|
||||
switch self {
|
||||
@@ -3303,6 +3509,12 @@ public enum RcvGroupEvent: Decodable {
|
||||
case .memberLeft: return NSLocalizedString("left", comment: "rcv group event chat item")
|
||||
case let .memberRole(_, profile, role):
|
||||
return String.localizedStringWithFormat(NSLocalizedString("changed role of %@ to %@", comment: "rcv group event chat item"), profile.profileViewName, role.text)
|
||||
case let .memberBlocked(_, profile, blocked):
|
||||
if blocked {
|
||||
return String.localizedStringWithFormat(NSLocalizedString("blocked %@", comment: "rcv group event chat item"), profile.profileViewName)
|
||||
} else {
|
||||
return String.localizedStringWithFormat(NSLocalizedString("unblocked %@", comment: "rcv group event chat item"), profile.profileViewName)
|
||||
}
|
||||
case let .userRole(role):
|
||||
return String.localizedStringWithFormat(NSLocalizedString("changed your role to %@", comment: "rcv group event chat item"), role.text)
|
||||
case let .memberDeleted(_, profile):
|
||||
@@ -3312,6 +3524,20 @@ public enum RcvGroupEvent: Decodable {
|
||||
case .groupUpdated: return NSLocalizedString("updated group profile", comment: "rcv group event chat item")
|
||||
case .invitedViaGroupLink: return NSLocalizedString("invited via your group link", comment: "rcv group event chat item")
|
||||
case .memberCreatedContact: return NSLocalizedString("connected directly", comment: "rcv group event chat item")
|
||||
case let .memberProfileUpdated(fromProfile, toProfile): return profileUpdatedText(fromProfile, toProfile)
|
||||
}
|
||||
}
|
||||
|
||||
private func profileUpdatedText(_ from: Profile, _ to: Profile) -> String {
|
||||
if to.displayName != from.displayName || to.fullName != from.fullName {
|
||||
String.localizedStringWithFormat(NSLocalizedString("member %@ changed to %@", comment: "profile update event chat item"), from.profileViewName, to.profileViewName)
|
||||
} else if to.image != from.image {
|
||||
to.image == nil
|
||||
? NSLocalizedString("removed profile picture", comment: "profile update event chat item")
|
||||
: NSLocalizedString("set new profile picture", comment: "profile update event chat item")
|
||||
} else {
|
||||
// shouldn't happen if backend correctly creates item; UI should be synchronized with backend
|
||||
NSLocalizedString("updated profile", comment: "profile update event chat item")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3319,6 +3545,7 @@ public enum RcvGroupEvent: Decodable {
|
||||
public enum SndGroupEvent: Decodable {
|
||||
case memberRole(groupMemberId: Int64, profile: Profile, role: GroupMemberRole)
|
||||
case userRole(role: GroupMemberRole)
|
||||
case memberBlocked(groupMemberId: Int64, profile: Profile, blocked: Bool)
|
||||
case memberDeleted(groupMemberId: Int64, profile: Profile)
|
||||
case userLeft
|
||||
case groupUpdated(groupProfile: GroupProfile)
|
||||
@@ -3329,6 +3556,12 @@ public enum SndGroupEvent: Decodable {
|
||||
return String.localizedStringWithFormat(NSLocalizedString("you changed role of %@ to %@", comment: "snd group event chat item"), profile.profileViewName, role.text)
|
||||
case let .userRole(role):
|
||||
return String.localizedStringWithFormat(NSLocalizedString("you changed role for yourself to %@", comment: "snd group event chat item"), role.text)
|
||||
case let .memberBlocked(_, profile, blocked):
|
||||
if blocked {
|
||||
return String.localizedStringWithFormat(NSLocalizedString("you blocked %@", comment: "snd group event chat item"), profile.profileViewName)
|
||||
} else {
|
||||
return String.localizedStringWithFormat(NSLocalizedString("you unblocked %@", comment: "snd group event chat item"), profile.profileViewName)
|
||||
}
|
||||
case let .memberDeleted(_, profile):
|
||||
return String.localizedStringWithFormat(NSLocalizedString("you removed %@", comment: "snd group event chat item"), profile.profileViewName)
|
||||
case .userLeft: return NSLocalizedString("you left", comment: "snd group event chat item")
|
||||
|
||||
@@ -22,6 +22,8 @@ public let MAX_VIDEO_SIZE_AUTO_RCV: Int64 = 1_047_552 // 1023KB
|
||||
|
||||
public let MAX_FILE_SIZE_XFTP: Int64 = 1_073_741_824 // 1GB
|
||||
|
||||
public let MAX_FILE_SIZE_LOCAL: Int64 = Int64.max
|
||||
|
||||
public let MAX_FILE_SIZE_SMP: Int64 = 8000000
|
||||
|
||||
public let MAX_VOICE_MESSAGE_LENGTH = TimeInterval(300)
|
||||
@@ -69,13 +71,29 @@ func fileModificationDate(_ path: String) -> Date? {
|
||||
}
|
||||
}
|
||||
|
||||
public func deleteAppDatabaseAndFiles() {
|
||||
let fm = FileManager.default
|
||||
let dbPath = getAppDatabasePath().path
|
||||
do {
|
||||
try fm.removeItem(atPath: dbPath + CHAT_DB)
|
||||
try fm.removeItem(atPath: dbPath + AGENT_DB)
|
||||
} catch let error {
|
||||
logger.error("Failed to delete all databases: \(error)")
|
||||
}
|
||||
try? fm.removeItem(atPath: dbPath + CHAT_DB_BAK)
|
||||
try? fm.removeItem(atPath: dbPath + AGENT_DB_BAK)
|
||||
try? fm.removeItem(at: getTempFilesDirectory())
|
||||
try? fm.createDirectory(at: getTempFilesDirectory(), withIntermediateDirectories: true)
|
||||
deleteAppFiles()
|
||||
_ = kcDatabasePassword.remove()
|
||||
storeDBPassphraseGroupDefault.set(true)
|
||||
}
|
||||
|
||||
public func deleteAppFiles() {
|
||||
let fm = FileManager.default
|
||||
do {
|
||||
let fileNames = try fm.contentsOfDirectory(atPath: getAppFilesDirectory().path)
|
||||
for fileName in fileNames {
|
||||
removeFile(fileName)
|
||||
}
|
||||
try fm.removeItem(at: getAppFilesDirectory())
|
||||
try fm.createDirectory(at: getAppFilesDirectory(), withIntermediateDirectories: true)
|
||||
} catch {
|
||||
logger.error("FileUtils deleteAppFiles error: \(error.localizedDescription)")
|
||||
}
|
||||
@@ -224,6 +242,7 @@ public func getMaxFileSize(_ fileProtocol: FileProtocol) -> Int64 {
|
||||
switch fileProtocol {
|
||||
case .xftp: return MAX_FILE_SIZE_XFTP
|
||||
case .smp: return MAX_FILE_SIZE_SMP
|
||||
case .local: return MAX_FILE_SIZE_LOCAL
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ extern char *chat_parse_markdown(char *str);
|
||||
extern char *chat_parse_server(char *str);
|
||||
extern char *chat_password_hash(char *pwd, char *salt);
|
||||
extern char *chat_valid_name(char *name);
|
||||
extern int chat_json_length(char *str);
|
||||
extern char *chat_encrypt_media(chat_ctrl ctl, char *key, char *frame, int len);
|
||||
extern char *chat_decrypt_media(char *key, char *frame, int len);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,9 @@
|
||||
/* Privacy - Face ID Usage Description */
|
||||
"NSFaceIDUsageDescription" = "SimpleX използва Face ID за локалнa идентификация";
|
||||
|
||||
/* Privacy - Local Network Usage Description */
|
||||
"NSLocalNetworkUsageDescription" = "SimpleX използва достъп до локална мрежа, за да позволи използването на потребителския чат профил чрез настолно приложение в същата мрежа.";
|
||||
|
||||
/* Privacy - Microphone Usage Description */
|
||||
"NSMicrophoneUsageDescription" = "SimpleX се нуждае от достъп до микрофона за аудио и видео разговори и за запис на гласови съобщения.";
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"_italic_" = "\\_kurzíva_";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"- connect to [directory service](simplex:/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) (BETA)!\n- delivery receipts (up to 20 members).\n- faster and more stable." = "- připojit k [adresářová služba](simplex:/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.cibule) (BETA)!\n- doručenky (až 20 členů).\n- Rychlejší a stabilnější.";
|
||||
"- connect to [directory service](simplex:/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) (BETA)!\n- delivery receipts (up to 20 members).\n- faster and more stable." = "- připojit k [adresářová služba](simplex:/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) (BETA)!\n- doručenky (až 20 členů).\n- Rychlejší a stabilnější.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"- more stable message delivery.\n- a bit better groups.\n- and more!" = "- více stabilní doručování zpráv.\n- o trochu lepší skupiny.\n- a více!";
|
||||
@@ -58,9 +58,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"**Add new contact**: to create your one-time QR Code for your contact." = "**Přidat nový kontakt**: pro vytvoření jednorázového QR kódu nebo odkazu pro váš kontakt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Create link / QR code** for your contact to use." = "**Vytvořte odkaz / QR kód** pro váš kontakt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**e2e encrypted** audio call" = "**e2e šifrovaný** audio hovor";
|
||||
|
||||
@@ -73,18 +70,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Nejsoukromější**: nepoužívejte server oznámení SimpleX Chat, pravidelně kontrolujte zprávy na pozadí (závisí na tom, jak často aplikaci používáte).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Paste received link** or open it in the browser and tap **Open in mobile app**." = "**Vložte přijatý odkaz** nebo jej otevřete v prohlížeči a klepněte na **Otevřít v mobilní aplikaci**.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Upozornění**: Pokud heslo ztratíte, NEBUDETE jej moci obnovit ani změnit.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Doporučeno**: Token zařízení a oznámení se odesílají na oznamovací server SimpleX Chat, ale nikoli obsah, velikost nebo od koho jsou zprávy.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Scan QR code**: to connect to your contact in person or via video call." = "** Naskenujte QR kód**: pro připojení ke kontaktu osobně nebo prostřednictvím videohovoru.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Upozornění**: Okamžitě doručovaná oznámení vyžadují přístupové heslo uložené v Klíčence.";
|
||||
|
||||
@@ -118,12 +109,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"%@ %@" = "%@ %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%@ and %@" = "%@ a %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%@ and %@ connected" = "%@ a %@ připojen";
|
||||
|
||||
/* copied message info, <sender> at <time> */
|
||||
"%@ at %@:" = "%1$@ na %2$@:";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%@ connected" = "%@ připojen";
|
||||
|
||||
/* notification title */
|
||||
"%@ is connected!" = "%@ je připojen!";
|
||||
|
||||
@@ -247,9 +244,6 @@
|
||||
/* time interval */
|
||||
"1 week" = "1 týden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"1-time link" = "Jednorázový odkaz";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"5 minutes" = "5 minut";
|
||||
|
||||
@@ -387,7 +381,7 @@
|
||||
"Allow disappearing messages only if your contact allows it to you." = "Povolte mizící zprávy, pouze pokud vám to váš kontakt dovolí.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow irreversible message deletion only if your contact allows it to you." = "Povolte nevratné smazání zprávy pouze v případě, že vám to váš kontakt dovolí.";
|
||||
"Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Povolte nevratné smazání zprávy pouze v případě, že vám to váš kontakt dovolí. (24 hodin)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow message reactions only if your contact allows them." = "Povolit reakce na zprávy, pokud je váš kontakt povolí.";
|
||||
@@ -402,7 +396,7 @@
|
||||
"Allow sending disappearing messages." = "Povolit odesílání mizících zpráv.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow to irreversibly delete sent messages." = "Povolit nevratné smazání odeslaných zpráv.";
|
||||
"Allow to irreversibly delete sent messages. (24 hours)" = "Povolit nevratné smazání odeslaných zpráv. (24 hodin)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow to send files and media." = "Povolit odesílání souborů a médii.";
|
||||
@@ -423,7 +417,7 @@
|
||||
"Allow your contacts to call you." = "Povolte svým kontaktům vám volat.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow your contacts to irreversibly delete sent messages." = "Umožněte svým kontaktům nevratně odstranit odeslané zprávy.";
|
||||
"Allow your contacts to irreversibly delete sent messages. (24 hours)" = "Umožněte svým kontaktům nevratně odstranit odeslané zprávy. (24 hodin)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow your contacts to send disappearing messages." = "Umožněte svým kontaktům odesílat mizící zprávy.";
|
||||
@@ -534,7 +528,7 @@
|
||||
"Both you and your contact can add message reactions." = "Vy i váš kontakt můžete přidávat reakce na zprávy.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Both you and your contact can irreversibly delete sent messages." = "Vy i váš kontakt můžete nevratně mazat odeslané zprávy.";
|
||||
"Both you and your contact can irreversibly delete sent messages. (24 hours)" = "Vy i váš kontakt můžete nevratně mazat odeslané zprávy. (24 hodin)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Both you and your contact can make calls." = "Volat můžete vy i váš kontakt.";
|
||||
@@ -566,9 +560,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Calls" = "Hovory";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Can't delete user profile!" = "Nemohu smazat uživatelský profil!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Can't invite contact!" = "Nelze pozvat kontakt!";
|
||||
|
||||
@@ -729,9 +720,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via link" = "Připojte se prostřednictvím odkazu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via link / QR code" = "Připojit se prostřednictvím odkazu / QR kódu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via one-time link" = "Připojit se jednorázovým odkazem";
|
||||
|
||||
@@ -849,9 +837,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" = "Vytvořit nový profil v [desktop app](https://simplex.chat/downloads/). 💻";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create one-time invitation link" = "Vytvořit jednorázovou pozvánku";
|
||||
|
||||
/* server test step */
|
||||
"Create queue" = "Vytvořit frontu";
|
||||
|
||||
@@ -1621,7 +1606,7 @@
|
||||
"Group members can add message reactions." = "Členové skupin mohou přidávat reakce na zprávy.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group members can irreversibly delete sent messages." = "Členové skupiny mohou nevratně mazat odeslané zprávy.";
|
||||
"Group members can irreversibly delete sent messages. (24 hours)" = "Členové skupiny mohou nevratně mazat odeslané zprávy. (24 hodin)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group members can send direct messages." = "Členové skupiny mohou posílat přímé zprávy.";
|
||||
@@ -1713,9 +1698,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"If you can't meet in person, show QR code in a video call, or share the link." = "Pokud se nemůžete setkat osobně, zobrazte QR kód ve videohovoru nebo sdílejte odkaz.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link." = "Pokud se nemůžete setkat osobně, můžete **naskenovat QR kód během videohovoru**, nebo váš kontakt může sdílet odkaz na pozvánku.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"If you enter this passcode when opening the app, all app data will be irreversibly removed!" = "Pokud tento přístupový kód zadáte při otevření aplikace, všechna data budou nenávratně smazána!";
|
||||
|
||||
@@ -1956,6 +1938,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Live messages" = "Živé zprávy";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Local" = "Místní";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Local name" = "Místní název";
|
||||
|
||||
@@ -2219,9 +2204,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Off" = "Vypnout";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Off (Local)" = "Vypnuto (místní)";
|
||||
|
||||
/* feature offered item */
|
||||
"offered %@" = "nabídl %@";
|
||||
|
||||
@@ -2268,7 +2250,7 @@
|
||||
"Only you can add message reactions." = "Reakce na zprávy můžete přidávat pouze vy.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only you can irreversibly delete messages (your contact can mark them for deletion)." = "Nevratně mazat zprávy můžete pouze vy (váš kontakt je může označit ke smazání).";
|
||||
"Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)" = "Nevratně mazat zprávy můžete pouze vy (váš kontakt je může označit ke smazání). (24 hodin)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only you can make calls." = "Volat můžete pouze vy.";
|
||||
@@ -2283,7 +2265,7 @@
|
||||
"Only your contact can add message reactions." = "Reakce na zprávy může přidávat pouze váš kontakt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only your contact can irreversibly delete messages (you can mark them for deletion)." = "Nevratně mazat zprávy může pouze váš kontakt (vy je můžete označit ke smazání).";
|
||||
"Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours)" = "Nevratně mazat zprávy může pouze váš kontakt (vy je můžete označit ke smazání). (24 hodin)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only your contact can make calls." = "Volat může pouze váš kontakt.";
|
||||
@@ -2312,9 +2294,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Open-source protocol and code – anybody can run the servers." = "Protokol a kód s otevřeným zdrojovým kódem - servery může provozovat kdokoli.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Opening database…" = "Otvírání databáze…";
|
||||
|
||||
/* member role */
|
||||
"owner" = "vlastník";
|
||||
|
||||
@@ -2336,18 +2315,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Password to show" = "Heslo k zobrazení";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Paste" = "Vložit";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Paste image" = "Vložit obrázek";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Paste received link" = "Vložení přijatého odkazu";
|
||||
|
||||
/* placeholder */
|
||||
"Paste the link you received to connect with your contact." = "Vložte odkaz, který jste obdrželi, do pole níže a spojte se se svým kontaktem.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"peer-to-peer" = "peer-to-peer";
|
||||
|
||||
@@ -2897,9 +2867,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Share link" = "Sdílet odkaz";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share one-time invitation link" = "Jednorázový zvací odkaz";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share with contacts" = "Sdílet s kontakty";
|
||||
|
||||
@@ -2975,9 +2942,6 @@
|
||||
/* notification title */
|
||||
"Somebody" = "Někdo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Start a new chat" = "Začít nový chat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Start chat" = "Začít chat";
|
||||
|
||||
@@ -3137,12 +3101,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Theme" = "Téma";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"There should be at least one user profile." = "Měl by tam být alespoň jeden uživatelský profil.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"There should be at least one visible user profile." = "Měl by tam být alespoň jeden viditelný uživatelský profil.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"These settings are for your current profile **%@**." = "Toto nastavení je pro váš aktuální profil **%@**.";
|
||||
|
||||
@@ -3215,9 +3173,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Turn off" = "Vypnout";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Turn off notifications?" = "Vypnout upozornění?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Turn on" = "Zapnout";
|
||||
|
||||
@@ -3485,9 +3440,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You can accept calls from lock screen, without device and app authentication." = "Můžete přijímat hovory z obrazovky zámku, bez ověření zařízení a aplikace.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button." = "Můžete se také připojit kliknutím na odkaz. Pokud se otevře v prohlížeči, klikněte na tlačítko **Otevřít v mobilní aplikaci**.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can create it later" = "Můžete vytvořit později";
|
||||
|
||||
@@ -3638,9 +3590,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Your contacts can allow full message deletion." = "Vaše kontakty mohou povolit úplné mazání zpráv.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contacts in SimpleX will see it.\nYou can change it in Settings." = "Vaše kontakty v SimpleX ji uvidí.\nMůžete ji změnit v Nastavení.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contacts will remain connected." = "Vaše kontakty zůstanou připojeny.";
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"- optionally notify deleted contacts.\n- profile names with spaces.\n- and more!" = "- Optionale Benachrichtigung von gelöschten Kontakten.\n- Profilnamen mit Leerzeichen.\n- Und mehr!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- Bis zu 5 Minuten lange Sprachnachrichten.\n- Zeitdauer für verschwindende Nachrichten anpassen.\n- Nachrichten-Historie bearbeiten.";
|
||||
"- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- Bis zu 5 Minuten lange Sprachnachrichten\n- Zeitdauer für verschwindende Nachrichten anpassen\n- Nachrichtenverlauf bearbeiten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
", " = ", ";
|
||||
@@ -65,10 +65,13 @@
|
||||
"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Stern auf GitHub vergeben](https://github.com/simplex-chat/simplex-chat)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Add new contact**: to create your one-time QR Code for your contact." = "**Fügen Sie einen neuen Kontakt hinzu**: Erzeugen Sie einen Einmal-QR-Code oder -Link für Ihren Kontakt.";
|
||||
"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Kontakt hinzufügen**: Um einen neuen Einladungslink zu erstellen oder eine Verbindung über einen Link herzustellen, den Sie erhalten haben.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Create link / QR code** for your contact to use." = "**Generieren Sie einen Einladungs-Link / QR code** für Ihren Kontakt.";
|
||||
"**Add new contact**: to create your one-time QR Code for your contact." = "**Neuen Kontakt hinzufügen**: Um einen Einmal-QR-Code oder -Link für Ihren Kontakt zu erzeugen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Create group**: to create a new group." = "**Gruppe erstellen**: Um eine neue Gruppe zu erstellen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**e2e encrypted** audio call" = "**E2E-verschlüsselter** Audioanruf";
|
||||
@@ -82,18 +85,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Beste Privatsphäre**: Es wird kein SimpleX-Chat-Benachrichtigungs-Server genutzt, Nachrichten werden in periodischen Abständen im Hintergrund geprüft (dies hängt davon ab, wie häufig Sie die App nutzen).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Paste received link** or open it in the browser and tap **Open in mobile app**." = "**Fügen Sie den von Ihrem Kontakt erhaltenen Link ein** oder öffnen Sie ihn im Browser und tippen Sie auf **In mobiler App öffnen**.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Bitte beachten Sie**: Das Passwort kann NICHT wiederhergestellt oder geändert werden, wenn Sie es vergessen haben oder verlieren.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Empfohlen**: Nur Ihr Geräte-Token und ihre Benachrichtigungen werden an den SimpleX-Chat-Benachrichtigungs-Server gesendet, aber weder der Nachrichteninhalt noch deren Größe oder von wem sie gesendet wurde.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Scan QR code**: to connect to your contact in person or via video call." = "**Scannen Sie den QR-Code**, um sich während einem persönlichen Treffen oder per Videoanruf mit Ihrem Kontakt zu verbinden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Warnung**: Sofortige Push-Benachrichtigungen erfordern die Eingabe eines Passworts, welches in Ihrem Schlüsselbund gespeichert ist.";
|
||||
|
||||
@@ -104,7 +101,7 @@
|
||||
"# %@" = "# %@";
|
||||
|
||||
/* copied message info */
|
||||
"## History" = "## Vergangenheit";
|
||||
"## History" = "## Nachrichtenverlauf";
|
||||
|
||||
/* copied message info */
|
||||
"## In reply to" = "## Als Antwort auf";
|
||||
@@ -205,6 +202,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"%lld messages blocked" = "%lld Nachrichten blockiert";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lld messages blocked by admin" = "%lld Nachrichten wurden vom Administrator blockiert";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lld messages marked deleted" = "%lld Nachrichten als gelöscht markiert";
|
||||
|
||||
@@ -280,9 +280,6 @@
|
||||
/* time interval */
|
||||
"1 week" = "wöchentlich";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"1-time link" = "Einmal-Link";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"5 minutes" = "5 Minuten";
|
||||
|
||||
@@ -311,10 +308,10 @@
|
||||
"Abort" = "Abbrechen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Abort changing address" = "Wechsel der Adresse abbrechen";
|
||||
"Abort changing address" = "Wechsel der Empfängeradresse abbrechen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Abort changing address?" = "Wechsel der Adresse abbrechen?";
|
||||
"Abort changing address?" = "Wechsel der Empfängeradresse abbrechen?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"About SimpleX" = "Über SimpleX";
|
||||
@@ -348,7 +345,10 @@
|
||||
"accepted call" = "Anruf angenommen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Fügen Sie die Adresse zu Ihrem Profil hinzu, damit Ihre Kontakte sie mit anderen Personen teilen können. Es wird eine Profilaktualisierung an Ihre Kontakte gesendet.";
|
||||
"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Fügen Sie die Adresse Ihrem Profil hinzu, damit Ihre Kontakte sie mit anderen Personen teilen können. Es wird eine Profilaktualisierung an Ihre Kontakte gesendet.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add contact" = "Kontakt hinzufügen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add preset servers" = "Füge voreingestellte Server hinzu";
|
||||
@@ -372,7 +372,7 @@
|
||||
"Address" = "Adresse";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Address change will be aborted. Old receiving address will be used." = "Der Wechsel der Adresse wird abgebrochen. Die bisherige Adresse wird weiter verwendet.";
|
||||
"Address change will be aborted. Old receiving address will be used." = "Der Wechsel der Empfängeradresse wird abgebrochen. Die bisherige Adresse wird weiter verwendet.";
|
||||
|
||||
/* member role */
|
||||
"admin" = "Admin";
|
||||
@@ -401,11 +401,14 @@
|
||||
/* No comment provided by engineer. */
|
||||
"All group members will remain connected." = "Alle Gruppenmitglieder bleiben verbunden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"All messages will be deleted - this cannot be undone!" = "Es werden alle Nachrichten gelöscht. Dieser Vorgang kann nicht rückgängig gemacht werden!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "Alle Nachrichten werden gelöscht - dies kann nicht rückgängig gemacht werden! Die Nachrichten werden NUR bei Ihnen gelöscht.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"All new messages from %@ will be hidden!" = "Alle neuen Nachrichten von %@ werden verborgen!";
|
||||
"All new messages from %@ will be hidden!" = "Von %@ werden alle neuen Nachrichten ausgeblendet!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"All your contacts will remain connected." = "Alle Ihre Kontakte bleiben verbunden.";
|
||||
@@ -420,10 +423,10 @@
|
||||
"Allow calls only if your contact allows them." = "Erlauben Sie Anrufe nur dann, wenn es Ihr Kontakt ebenfalls erlaubt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow disappearing messages only if your contact allows it to you." = "Erlauben Sie verschwindende Nachrichten nur dann, wenn es Ihnen Ihr Kontakt ebenfalls erlaubt.";
|
||||
"Allow disappearing messages only if your contact allows it to you." = "Erlauben Sie verschwindende Nachrichten nur dann, wenn es Ihr Kontakt ebenfalls erlaubt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow irreversible message deletion only if your contact allows it to you." = "Erlauben Sie das unwiederbringliche Löschen von Nachrichten nur dann, wenn es Ihnen Ihr Kontakt ebenfalls erlaubt.";
|
||||
"Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Erlauben Sie das unwiederbringliche Löschen von Nachrichten nur dann, wenn es Ihnen Ihr Kontakt ebenfalls erlaubt. (24 Stunden)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow message reactions only if your contact allows them." = "Erlauben Sie Reaktionen auf Nachrichten nur dann, wenn es Ihr Kontakt ebenfalls erlaubt.";
|
||||
@@ -438,7 +441,7 @@
|
||||
"Allow sending disappearing messages." = "Das Senden von verschwindenden Nachrichten erlauben.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow to irreversibly delete sent messages." = "Unwiederbringliches löschen von gesendeten Nachrichten erlauben.";
|
||||
"Allow to irreversibly delete sent messages. (24 hours)" = "Unwiederbringliches löschen von gesendeten Nachrichten erlauben. (24 Stunden)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow to send files and media." = "Das Senden von Dateien und Medien erlauben.";
|
||||
@@ -459,7 +462,7 @@
|
||||
"Allow your contacts to call you." = "Erlaubt Ihren Kontakten Sie anzurufen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow your contacts to irreversibly delete sent messages." = "Erlauben Sie Ihren Kontakten gesendete Nachrichten unwiederbringlich zu löschen.";
|
||||
"Allow your contacts to irreversibly delete sent messages. (24 hours)" = "Erlauben Sie Ihren Kontakten gesendete Nachrichten unwiederbringlich zu löschen. (24 Stunden)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow your contacts to send disappearing messages." = "Erlauben Sie Ihren Kontakten das Senden von verschwindenden Nachrichten.";
|
||||
@@ -584,17 +587,32 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Block" = "Blockieren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Block for all" = "Für Alle blockieren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Block group members" = "Gruppenmitglieder blockieren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Block member" = "Mitglied blockieren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Block member for all?" = "Mitglied für Alle blockieren?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Block member?" = "Mitglied blockieren?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"blocked" = "blockiert";
|
||||
"blocked" = "Blockiert";
|
||||
|
||||
/* rcv group event chat item */
|
||||
"blocked %@" = "%@ wurde blockiert";
|
||||
|
||||
/* blocked chat item */
|
||||
"blocked by admin" = "wurde vom Administrator blockiert";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Blocked by admin" = "wurde vom Administrator blockiert";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"bold" = "fett";
|
||||
@@ -603,7 +621,7 @@
|
||||
"Both you and your contact can add message reactions." = "Sowohl Sie, als auch Ihr Kontakt können Reaktionen auf Nachrichten geben.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Both you and your contact can irreversibly delete sent messages." = "Sowohl Ihr Kontakt, als auch Sie können gesendete Nachrichten unwiederbringlich löschen.";
|
||||
"Both you and your contact can irreversibly delete sent messages. (24 hours)" = "Sowohl Ihr Kontakt, als auch Sie können gesendete Nachrichten unwiederbringlich löschen. (24 Stunden)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Both you and your contact can make calls." = "Sowohl Sie, als auch Ihr Kontakt können Anrufe tätigen.";
|
||||
@@ -636,7 +654,7 @@
|
||||
"Calls" = "Anrufe";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Can't delete user profile!" = "Das Benutzerprofil kann nicht gelöscht werden!";
|
||||
"Camera not available" = "Kamera nicht verfügbar";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Can't invite contact!" = "Kontakt kann nicht eingeladen werden!";
|
||||
@@ -688,7 +706,7 @@
|
||||
"Change self-destruct passcode" = "Selbstzerstörungs-Zugangscode ändern";
|
||||
|
||||
/* chat item text */
|
||||
"changed address for you" = "wechselte die Adresse für Sie";
|
||||
"changed address for you" = "Wechselte die Empfängeradresse von Ihnen";
|
||||
|
||||
/* rcv group event chat item */
|
||||
"changed role of %@ to %@" = "änderte die Rolle von %1$@ auf %2$@";
|
||||
@@ -697,10 +715,10 @@
|
||||
"changed your role to %@" = "änderte Ihre Rolle auf %@";
|
||||
|
||||
/* chat item text */
|
||||
"changing address for %@…" = "Adresse von %@ wechseln…";
|
||||
"changing address for %@…" = "Empfängeradresse für %@ wechseln wird gestartet…";
|
||||
|
||||
/* chat item text */
|
||||
"changing address…" = "Wechsel der Adresse…";
|
||||
"changing address…" = "Wechsel der Empfängeradresse wurde gestartet…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Chat archive" = "Datenbank Archiv";
|
||||
@@ -723,6 +741,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Chat is stopped" = "Der Chat ist beendet";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "Der Chat ist angehalten. Wenn Sie diese Datenbank bereits auf einem anderen Gerät genutzt haben, sollten Sie diese vor dem Starten des Chats wieder zurückspielen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Chat preferences" = "Chat-Präferenzen";
|
||||
|
||||
@@ -750,6 +771,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Clear conversation?" = "Unterhaltung löschen?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Clear private notes?" = "Private Notizen löschen?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Clear verification" = "Überprüfung zurücknehmen";
|
||||
|
||||
@@ -808,7 +832,7 @@
|
||||
"Connect to yourself?\nThis is your own one-time link!" = "Mit Ihnen selbst verbinden?\nDas ist Ihr eigener Einmal-Link!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect to yourself?\nThis is your own SimpleX address!" = "Mit Ihnen selbst verbinden?\nDas ist Ihre eigene SimpleX-Adresse!";
|
||||
"Connect to yourself?\nThis is your own SimpleX address!" = "Sich mit Ihnen selbst verbinden?\nDas ist Ihre eigene SimpleX-Adresse!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via contact address" = "Über die Kontakt-Adresse verbinden";
|
||||
@@ -816,9 +840,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via link" = "Über einen Link verbinden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via link / QR code" = "Über einen Link / QR-Code verbinden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via one-time link" = "Über einen Einmal-Link verbinden";
|
||||
|
||||
@@ -891,6 +912,9 @@
|
||||
/* connection information */
|
||||
"connection:%@" = "Verbindung:%@";
|
||||
|
||||
/* profile update event chat item */
|
||||
"contact %@ changed to %@" = "Der Kontaktname %1$@ wurde auf %2$@ geändert";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact allows" = "Der Kontakt erlaubt";
|
||||
|
||||
@@ -960,9 +984,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" = "Neues Profil in der [Desktop-App] erstellen (https://simplex.chat/downloads/). 💻";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create one-time invitation link" = "Einmal-Einladungslink erstellen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create profile" = "Profil erstellen";
|
||||
|
||||
@@ -978,9 +999,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Create your profile" = "Erstellen Sie Ihr Profil";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Created at" = "Erstellt um";
|
||||
|
||||
/* copied message info */
|
||||
"Created at: %@" = "Erstellt um: %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Created on %@" = "Erstellt am %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Creating link…" = "Link wird erstellt…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"creator" = "Ersteller";
|
||||
|
||||
@@ -1270,7 +1300,7 @@
|
||||
"Disappearing message" = "Verschwindende Nachricht";
|
||||
|
||||
/* chat feature */
|
||||
"Disappearing messages" = "verschwindende Nachrichten";
|
||||
"Disappearing messages" = "Verschwindende Nachrichten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Disappearing messages are prohibited in this chat." = "In diesem Chat sind verschwindende Nachrichten nicht erlaubt.";
|
||||
@@ -1299,6 +1329,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Do it later" = "Später wiederholen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Do not send history to new members." = "Den Nachrichtenverlauf nicht an neue Mitglieder senden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Do NOT use SimpleX for emergency calls." = "Nutzen Sie SimpleX nicht für Notrufe.";
|
||||
|
||||
@@ -1344,6 +1377,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Enable automatic message deletion?" = "Automatisches Löschen von Nachrichten aktivieren?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable camera access" = "Kamera-Zugriff aktivieren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable for all" = "Für Alle aktivieren";
|
||||
|
||||
@@ -1398,6 +1434,9 @@
|
||||
/* notification */
|
||||
"Encrypted message or another event" = "Verschlüsselte Nachricht oder ein anderes Ereignis";
|
||||
|
||||
/* notification */
|
||||
"Encrypted message: app is stopped" = "Verschlüsselte Nachricht: Die App ist angehalten";
|
||||
|
||||
/* notification */
|
||||
"Encrypted message: database error" = "Verschlüsselte Nachricht: Datenbankfehler";
|
||||
|
||||
@@ -1498,7 +1537,7 @@
|
||||
"Error adding member(s)" = "Fehler beim Hinzufügen von Mitgliedern";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error changing address" = "Fehler beim Wechseln der Adresse";
|
||||
"Error changing address" = "Fehler beim Wechseln der Empfängeradresse";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error changing role" = "Fehler beim Ändern der Rolle";
|
||||
@@ -1518,6 +1557,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error creating member contact" = "Fehler beim Anlegen eines Mitglied-Kontaktes";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error creating message" = "Fehler beim Erstellen der Nachricht";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error creating profile!" = "Fehler beim Erstellen des Profils!";
|
||||
|
||||
@@ -1569,6 +1611,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error loading %@ servers" = "Fehler beim Laden von %@ Servern";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error opening chat" = "Fehler beim Öffnen des Chats";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error receiving file" = "Fehler beim Empfangen der Datei";
|
||||
|
||||
@@ -1593,6 +1638,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error saving user password" = "Fehler beim Speichern des Benutzer-Passworts";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error scanning code: %@" = "Fehler beim Scannen des Codes: %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error sending email" = "Fehler beim Senden der eMail";
|
||||
|
||||
@@ -1798,7 +1846,7 @@
|
||||
"Group members can add message reactions." = "Gruppenmitglieder können eine Reaktion auf Nachrichten geben.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group members can irreversibly delete sent messages." = "Gruppenmitglieder können gesendete Nachrichten unwiederbringlich löschen.";
|
||||
"Group members can irreversibly delete sent messages. (24 hours)" = "Gruppenmitglieder können gesendete Nachrichten unwiederbringlich löschen. (24 Stunden)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group members can send direct messages." = "Gruppenmitglieder können Direktnachrichten versenden.";
|
||||
@@ -1864,7 +1912,10 @@
|
||||
"Hide:" = "Verberge:";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"History" = "Vergangenheit";
|
||||
"History" = "Nachrichtenverlauf";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"History is not sent to new members." = "Der Nachrichtenverlauf wird nicht an neue Gruppenmitglieder gesendet.";
|
||||
|
||||
/* time unit */
|
||||
"hours" = "Stunden";
|
||||
@@ -1890,9 +1941,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"If you can't meet in person, show QR code in a video call, or share the link." = "Falls Sie sich nicht persönlich treffen können, zeigen Sie den QR-Code in einem Videoanruf oder teilen Sie den Link.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link." = "Wenn Sie sich nicht persönlich treffen können, kann der **QR-Code während eines Videoanrufs gescannt werden**, oder Ihr Kontakt kann den Einladungslink über einen anderen Kanal mit Ihnen teilen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"If you enter this passcode when opening the app, all app data will be irreversibly removed!" = "Wenn Sie diesen Zugangscode während des Öffnens der App eingeben, werden alle App-Daten unwiederbringlich gelöscht!";
|
||||
|
||||
@@ -1926,6 +1974,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Import database" = "Datenbank importieren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Improved message delivery" = "Verbesserte Zustellung von Nachrichten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Improved privacy and security" = "Verbesserte Privatsphäre und Sicherheit";
|
||||
|
||||
@@ -2010,9 +2061,21 @@
|
||||
/* invalid chat item */
|
||||
"invalid data" = "Ungültige Daten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid display name!" = "Ungültiger Anzeigename!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid link" = "Ungültiger Link";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid name!" = "Ungültiger Name!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid QR code" = "Ungültiger QR-Code";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid response" = "Ungültige Reaktion";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid server address!" = "Ungültige Serveradresse!";
|
||||
|
||||
@@ -2091,6 +2154,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Join group" = "Treten Sie der Gruppe bei";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Join group conversations" = "Gruppenunterhaltungen beitreten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Join group?" = "Der Gruppe beitreten?";
|
||||
|
||||
@@ -2106,9 +2172,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Joining group" = "Der Gruppe beitreten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Keep" = "Behalten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Keep the app open to use it from desktop" = "Die App muss geöffnet bleiben, um sie vom Desktop aus nutzen zu können";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Keep unused invitation?" = "Nicht genutzte Einladung behalten?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Keep your connections" = "Ihre Verbindungen beibehalten";
|
||||
|
||||
@@ -2163,6 +2235,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Live messages" = "Live Nachrichten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Local" = "Lokal";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Local name" = "Lokaler Name";
|
||||
|
||||
@@ -2188,7 +2263,7 @@
|
||||
"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Stellen Sie sicher, dass die %@-Server-Adressen das richtige Format haben, zeilenweise getrennt und nicht doppelt vorhanden sind (%@).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Stellen Sie sicher, dass die WebRTC ICE-Server Adressen das richtige Format haben, zeilenweise angeordnet und nicht doppelt vorhanden sind.";
|
||||
"Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Stellen Sie sicher, dass die WebRTC ICE-Server Adressen das richtige Format haben, zeilenweise getrennt und nicht doppelt vorhanden sind.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Viele Menschen haben gefragt: *Wie kann SimpleX Nachrichten zustellen, wenn es keine Benutzerkennungen gibt?*";
|
||||
@@ -2217,6 +2292,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Member" = "Mitglied";
|
||||
|
||||
/* profile update event chat item */
|
||||
"member %@ changed to %@" = "Der Mitgliedsname %1$@ wurde auf %2$@ geändert";
|
||||
|
||||
/* rcv group event chat item */
|
||||
"member connected" = "ist der Gruppe beigetreten";
|
||||
|
||||
@@ -2334,6 +2412,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"never" = "nie";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"New chat" = "Neuer Chat";
|
||||
|
||||
/* notification */
|
||||
"New contact request" = "Neue Kontaktanfrage";
|
||||
|
||||
@@ -2398,7 +2479,7 @@
|
||||
"No group!" = "Die Gruppe wurde nicht gefunden!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No history" = "Keine Vergangenheit";
|
||||
"No history" = "Kein Nachrichtenverlauf";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No permission to record voice message" = "Keine Berechtigung für das Aufnehmen von Sprachnachrichten";
|
||||
@@ -2432,9 +2513,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Off" = "Aus";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Off (Local)" = "Aus (Lokal)";
|
||||
|
||||
/* feature offered item */
|
||||
"offered %@" = "angeboten %@";
|
||||
|
||||
@@ -2444,6 +2522,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Ok" = "Ok";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"OK" = "OK";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Old database" = "Alte Datenbank";
|
||||
|
||||
@@ -2481,7 +2562,7 @@
|
||||
"Only you can add message reactions." = "Nur Sie können Reaktionen auf Nachrichten geben.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only you can irreversibly delete messages (your contact can mark them for deletion)." = "Nur Sie können Nachrichten unwiederbringlich löschen (Ihr Kontakt kann sie zum Löschen markieren).";
|
||||
"Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)" = "Nur Sie können Nachrichten unwiederbringlich löschen (Ihr Kontakt kann sie zum Löschen markieren). (24 Stunden)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only you can make calls." = "Nur Sie können Anrufe tätigen.";
|
||||
@@ -2496,7 +2577,7 @@
|
||||
"Only your contact can add message reactions." = "Nur Ihr Kontakt kann Reaktionen auf Nachrichten geben.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only your contact can irreversibly delete messages (you can mark them for deletion)." = "Nur Ihr Kontakt kann Nachrichten unwiederbringlich löschen (Sie können sie zum Löschen markieren).";
|
||||
"Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours)" = "Nur Ihr Kontakt kann Nachrichten unwiederbringlich löschen (Sie können sie zum Löschen markieren). (24 Stunden)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only your contact can make calls." = "Nur Ihr Kontakt kann Anrufe tätigen.";
|
||||
@@ -2529,7 +2610,13 @@
|
||||
"Open-source protocol and code – anybody can run the servers." = "Open-Source-Protokoll und -Code – Jede Person kann ihre eigenen Server aufsetzen und nutzen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Opening database…" = "Öffne Datenbank …";
|
||||
"Opening app…" = "App wird geöffnet…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Or scan QR code" = "Oder den QR-Code scannen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Or show this code" = "Oder diesen QR-Code anzeigen";
|
||||
|
||||
/* member role */
|
||||
"owner" = "Eigentümer";
|
||||
@@ -2552,8 +2639,8 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Password to show" = "Passwort anzeigen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Paste" = "Einfügen";
|
||||
/* past/unknown group member */
|
||||
"Past member %@" = "Ehemaliges Mitglied %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Paste desktop address" = "Desktop-Adresse einfügen";
|
||||
@@ -2562,10 +2649,10 @@
|
||||
"Paste image" = "Bild einfügen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Paste received link" = "Fügen Sie den erhaltenen Link ein";
|
||||
"Paste link to connect!" = "Zum Verbinden den Link einfügen!";
|
||||
|
||||
/* placeholder */
|
||||
"Paste the link you received to connect with your contact." = "Um sich mit Ihrem Kontakt zu verbinden, fügen Sie den erhaltenen Link in das Feld unten ein.";
|
||||
/* No comment provided by engineer. */
|
||||
"Paste the link you received" = "Fügen Sie den erhaltenen Link ein";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"peer-to-peer" = "Peer-to-Peer";
|
||||
@@ -2597,6 +2684,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Please check yours and your contact preferences." = "Bitte überprüfen sie sowohl Ihre, als auch die Präferenzen Ihres Kontakts.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Please contact developers.\nError: %@" = "Bitte nehmen Sie Kontakt mit den Entwicklern auf.\nFehler: %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Please contact group admin." = "Bitte kontaktieren Sie den Gruppen-Administrator.";
|
||||
|
||||
@@ -2648,6 +2738,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Private filenames" = "Neutrale Dateinamen";
|
||||
|
||||
/* name of notes to self */
|
||||
"Private notes" = "Private Notizen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Profile and server connections" = "Profil und Serververbindungen";
|
||||
|
||||
@@ -2720,6 +2813,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "Mehr dazu in der [Benutzeranleitung](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address) lesen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Lesen Sie mehr dazu im [Benutzerhandbuch](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Mehr dazu in der [Benutzeranleitung](https://simplex.chat/docs/guide/readme.html#connect-to-friends) lesen.";
|
||||
|
||||
@@ -2759,6 +2855,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Receiving via" = "Empfangen über";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Recent history and improved [directory bot](simplex:/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)." = "Aktueller Nachrichtenverlauf und verbesserter [Gruppenverzeichnis-Bot](simplex:/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).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Recipients see updates as you type them." = "Die Empfänger sehen Nachrichtenaktualisierungen, während Sie sie eingeben.";
|
||||
|
||||
@@ -2813,6 +2912,12 @@
|
||||
/* rcv group event chat item */
|
||||
"removed %@" = "hat %@ aus der Gruppe entfernt";
|
||||
|
||||
/* profile update event chat item */
|
||||
"removed contact address" = "Kontaktadresse wurde entfernt";
|
||||
|
||||
/* profile update event chat item */
|
||||
"removed profile picture" = "Profil-Bild wurde entfernt";
|
||||
|
||||
/* rcv group event chat item */
|
||||
"removed you" = "hat Sie aus der Gruppe entfernt";
|
||||
|
||||
@@ -2864,6 +2969,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Restore database error" = "Fehler bei der Wiederherstellung der Datenbank";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Retry" = "Wiederholen";
|
||||
|
||||
/* chat item action */
|
||||
"Reveal" = "Aufdecken";
|
||||
|
||||
@@ -2933,6 +3041,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Save welcome message?" = "Begrüßungsmeldung speichern?";
|
||||
|
||||
/* message info title */
|
||||
"Saved message" = "Gespeicherte Nachricht";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Saved WebRTC ICE servers will be removed" = "Gespeicherte WebRTC ICE-Server werden entfernt";
|
||||
|
||||
@@ -2954,6 +3065,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Search" = "Suche";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Search bar accepts invitation links." = "Von der Suchleiste werden Einladungslinks akzeptiert.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Search or paste SimpleX link" = "Suchen oder fügen Sie den SimpleX-Link ein";
|
||||
|
||||
/* network option */
|
||||
"sec" = "sek";
|
||||
|
||||
@@ -3032,6 +3149,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Send them from gallery or custom keyboards." = "Senden Sie diese aus dem Fotoalbum oder von individuellen Tastaturen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send up to 100 last messages to new members." = "Bis zu 100 der letzten Nachrichten an neue Gruppenmitglieder senden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sender cancelled file transfer." = "Der Absender hat die Dateiübertragung abgebrochen.";
|
||||
|
||||
@@ -3104,6 +3224,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Set it instead of system authentication." = "Anstelle der System-Authentifizierung festlegen.";
|
||||
|
||||
/* profile update event chat item */
|
||||
"set new contact address" = "Neue Kontaktadresse wurde festgelegt";
|
||||
|
||||
/* profile update event chat item */
|
||||
"set new profile picture" = "Neues Profil-Bild wurde festgelegt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Set passcode" = "Zugangscode einstellen";
|
||||
|
||||
@@ -3135,7 +3261,7 @@
|
||||
"Share link" = "Link teilen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share one-time invitation link" = "Einmal-Einladungslink teilen";
|
||||
"Share this 1-time invite link" = "Teilen Sie diesen Einmal-Einladungslink";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share with contacts" = "Mit Kontakten teilen";
|
||||
@@ -3213,10 +3339,10 @@
|
||||
"Somebody" = "Jemand";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Start a new chat" = "Starten Sie einen neuen Chat";
|
||||
"Start chat" = "Starten Sie den Chat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Start chat" = "Starten Sie den Chat";
|
||||
"Start chat?" = "Chat starten?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Start migration" = "Starten Sie die Migration";
|
||||
@@ -3276,19 +3402,25 @@
|
||||
"Tap button " = "Schaltfläche antippen ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to activate profile." = "Tippen Sie auf das Profil um es zu aktivieren.";
|
||||
"Tap to activate profile." = "Zum Aktivieren des Profils tippen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to Connect" = "Zum Verbinden antippen";
|
||||
"Tap to Connect" = "Zum Verbinden tippen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to join" = "Zum Beitreten tippen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to join incognito" = "Tippen, um Inkognito beizutreten";
|
||||
"Tap to join incognito" = "Zum Inkognito beitreten tippen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to start a new chat" = "Tippen, um einen neuen Chat zu starten";
|
||||
"Tap to paste link" = "Zum Link einfügen tippen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to scan" = "Zum Scannen tippen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to start a new chat" = "Zum Starten eines neuen Chats tippen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"TCP connection timeout" = "Timeout der TCP-Verbindung";
|
||||
@@ -3332,6 +3464,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"The attempt to change database passphrase was not completed." = "Die Änderung des Datenbank-Passworts konnte nicht abgeschlossen werden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The code you scanned is not a SimpleX link QR code." = "Der von Ihnen gescannte Code ist kein SimpleX-Link-QR-Code.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The connection you accepted will be cancelled!" = "Die von Ihnen akzeptierte Verbindung wird abgebrochen!";
|
||||
|
||||
@@ -3372,17 +3507,14 @@
|
||||
"The sender will NOT be notified" = "Der Absender wird NICHT benachrichtigt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The servers for new connections of your current chat profile **%@**." = "Server der neuen Verbindungen von Ihrem aktuellen Chat-Profil **%@**.";
|
||||
"The servers for new connections of your current chat profile **%@**." = "Mögliche Server für neue Verbindungen von Ihrem aktuellen Chat-Profil **%@**.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The text you pasted is not a SimpleX link." = "Der von Ihnen eingefügte Text ist kein SimpleX-Link.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Theme" = "Design";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"There should be at least one user profile." = "Es muss mindestens ein Benutzer-Profil vorhanden sein.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"There should be at least one visible user profile." = "Es muss mindestens ein sichtbares Benutzer-Profil vorhanden sein.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"These settings are for your current profile **%@**." = "Diese Einstellungen betreffen Ihr aktuelles Profil **%@**.";
|
||||
|
||||
@@ -3404,6 +3536,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"This device name" = "Dieser Gerätename";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"This display name is invalid. Please choose another name." = "Der Anzeigename ist ungültig. Bitte wählen Sie einen anderen Namen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"This group has over %lld members, delivery receipts are not sent." = "Es werden keine Empfangsbestätigungen gesendet, da diese Gruppe über %lld Mitglieder hat.";
|
||||
|
||||
@@ -3465,10 +3600,10 @@
|
||||
"Trying to connect to the server used to receive messages from this contact." = "Versuche die Verbindung mit dem Server aufzunehmen, der für den Empfang von Nachrichten mit diesem Kontakt genutzt wird.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Turn off" = "Abschalten";
|
||||
"Turkish interface" = "Türkische Bedienoberfläche";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Turn off notifications?" = "Benachrichtigungen abschalten?";
|
||||
"Turn off" = "Abschalten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Turn on" = "Einschalten";
|
||||
@@ -3479,12 +3614,21 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Unblock" = "Freigeben";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unblock for all" = "Für Alle freigeben";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unblock member" = "Mitglied freigeben";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unblock member for all?" = "Mitglied für Alle freigeben?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unblock member?" = "Mitglied freigeben?";
|
||||
|
||||
/* rcv group event chat item */
|
||||
"unblocked %@" = "%@ wurde freigegeben";
|
||||
|
||||
/* item status description */
|
||||
"Unexpected error: %@" = "Unerwarteter Fehler: %@";
|
||||
|
||||
@@ -3518,6 +3662,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Unknown error" = "Unbekannter Fehler";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"unknown status" = "unbekannter Gruppenmitglieds-Status";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions." = "Aktivieren Sie den Modus \"Bitte nicht stören\", um Unterbrechungen zu vermeiden, es sei denn, Sie verwenden die iOS Anrufschnittstelle.";
|
||||
|
||||
@@ -3542,6 +3689,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Unread" = "Ungelesen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Up to 100 last messages are sent to new members." = "Bis zu 100 der letzten Nachrichten werden an neue Mitglieder gesendet.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Update" = "Aktualisieren";
|
||||
|
||||
@@ -3560,6 +3710,9 @@
|
||||
/* rcv group event chat item */
|
||||
"updated group profile" = "Aktualisiertes Gruppenprofil";
|
||||
|
||||
/* profile update event chat item */
|
||||
"updated profile" = "Das Profil wurde aktualisiert";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Updating settings will re-connect the client to all servers." = "Die Aktualisierung der Einstellungen wird den Client wieder mit allen Servern verbinden.";
|
||||
|
||||
@@ -3593,6 +3746,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Use new incognito profile" = "Nutzen Sie das neue Inkognito-Profil";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use only local notifications?" = "Nur lokale Benachrichtigungen nutzen?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use server" = "Server nutzen";
|
||||
|
||||
@@ -3665,6 +3821,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"View security code" = "Schauen Sie sich den Sicherheitscode an";
|
||||
|
||||
/* chat feature */
|
||||
"Visible history" = "Sichtbarer Nachrichtenverlauf";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Voice message…" = "Sprachnachrichten…";
|
||||
|
||||
@@ -3728,9 +3887,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Wenn Sie ein Inkognito-Profil mit Jemandem teilen, wird dieses Profil auch für die Gruppen verwendet, für die Sie von diesem Kontakt eingeladen werden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"With encrypted files and media." = "Mit verschlüsselten Dateien und Medien.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"With optional welcome message." = "Mit optionaler Begrüßungsmeldung.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"With reduced battery usage." = "Mit reduziertem Akkuverbrauch.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Wrong database passphrase" = "Falsches Datenbank-Passwort";
|
||||
|
||||
@@ -3744,7 +3909,7 @@
|
||||
"yes" = "Ja";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You" = "Ihre Daten";
|
||||
"You" = "Profil";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You accepted connection" = "Sie haben die Verbindung akzeptiert";
|
||||
@@ -3791,11 +3956,11 @@
|
||||
/* No comment provided by engineer. */
|
||||
"you are observer" = "Sie sind Beobachter";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can accept calls from lock screen, without device and app authentication." = "Sie können Anrufe ohne Geräte- und App-Authentifizierung vom Sperrbildschirm aus annehmen.";
|
||||
/* snd group event chat item */
|
||||
"you blocked %@" = "Sie haben %@ blockiert";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button." = "Sie können sich auch verbinden, indem Sie auf den Link klicken. Wenn er im Browser geöffnet wird, klicken Sie auf die Schaltfläche **In mobiler App öffnen**.";
|
||||
"You can accept calls from lock screen, without device and app authentication." = "Sie können Anrufe ohne Geräte- und App-Authentifizierung vom Sperrbildschirm aus annehmen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can create it later" = "Sie können dies später erstellen";
|
||||
@@ -3809,6 +3974,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You can hide or mute a user profile - swipe it to the right." = "Sie können ein Benutzerprofil verbergen oder stummschalten - wischen Sie es nach rechts.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can make it visible to your SimpleX contacts via Settings." = "Sie können sie über Einstellungen für Ihre SimpleX-Kontakte sichtbar machen.";
|
||||
|
||||
/* notification body */
|
||||
"You can now send messages to %@" = "Sie können nun Nachrichten an %@ versenden";
|
||||
|
||||
@@ -3833,14 +4001,17 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You can use markdown to format messages:" = "Um Nachrichteninhalte zu formatieren, können Sie Markdowns verwenden:";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can view invitation link again in connection details." = "Den Einladungslink können Sie in den Details der Verbindung nochmals sehen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can't send messages!" = "Sie können keine Nachrichten versenden!";
|
||||
|
||||
/* chat item text */
|
||||
"you changed address" = "Sie haben die Adresse gewechselt";
|
||||
"you changed address" = "Die Empfängeradresse wurde gewechselt";
|
||||
|
||||
/* chat item text */
|
||||
"you changed address for %@" = "Sie haben die Adresse für %@ gewechselt";
|
||||
"you changed address for %@" = "Die Empfängeradresse für %@ wurde gewechselt";
|
||||
|
||||
/* snd group event chat item */
|
||||
"you changed role for yourself to %@" = "Sie haben Ihre eigene Rolle auf %@ geändert";
|
||||
@@ -3899,6 +4070,9 @@
|
||||
/* chat list item description */
|
||||
"you shared one-time link incognito" = "Sie haben Inkognito einen Einmal-Link geteilt";
|
||||
|
||||
/* snd group event chat item */
|
||||
"you unblocked %@" = "Sie haben %@ freigegeben";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You will be connected to group when the group host's device is online, please wait or check later!" = "Sie werden mit der Gruppe verbunden, sobald das Endgerät des Gruppen-Hosts online ist. Bitte warten oder schauen Sie später nochmal nach!";
|
||||
|
||||
@@ -3921,10 +4095,10 @@
|
||||
"You will still receive calls and notifications from muted profiles when they are active." = "Sie können Anrufe und Benachrichtigungen auch von stummgeschalteten Profilen empfangen, solange diese aktiv sind.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You will stop receiving messages from this group. Chat history will be preserved." = "Sie werden von dieser Gruppe keine Nachrichten mehr erhalten. Der Chatverlauf wird beibehalten.";
|
||||
"You will stop receiving messages from this group. Chat history will be preserved." = "Sie werden von dieser Gruppe keine Nachrichten mehr erhalten. Der Nachrichtenverlauf wird beibehalten.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You won't lose your contacts if you later delete your address." = "Sie werden Ihre mit dieser Adresse verbundenen Kontakte nicht verlieren, wenn Sie diese Adresse später löschen.";
|
||||
"You won't lose your contacts if you later delete your address." = "Sie werden Ihre damit verbundenen Kontakte nicht verlieren, wenn Sie diese Adresse später löschen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"you: " = "Sie: ";
|
||||
@@ -3959,9 +4133,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Your contacts can allow full message deletion." = "Ihre Kontakte können die unwiederbringliche Löschung von Nachrichten erlauben.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contacts in SimpleX will see it.\nYou can change it in Settings." = "Ihre Kontakte in SimpleX werden es sehen.\nSie können es in den Einstellungen ändern.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contacts will remain connected." = "Ihre Kontakte bleiben verbunden.";
|
||||
|
||||
@@ -4002,7 +4173,7 @@
|
||||
"Your server address" = "Ihre Serveradresse";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your settings" = "Ihre Einstellungen";
|
||||
"Your settings" = "Einstellungen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your SimpleX address" = "Ihre SimpleX-Adresse";
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,9 @@
|
||||
/* Privacy - Face ID Usage Description */
|
||||
"NSFaceIDUsageDescription" = "SimpleX usa reconocimiento facial para la autenticación local";
|
||||
|
||||
/* Privacy - Local Network Usage Description */
|
||||
"NSLocalNetworkUsageDescription" = "SimpleX utiliza el acceso a la red local para abrir el perfil de chat en la aplicación de ordenador en la misma red.";
|
||||
|
||||
/* Privacy - Microphone Usage Description */
|
||||
"NSMicrophoneUsageDescription" = "SimpleX necesita acceso al micrófono para las llamadas de audio, vídeo y para grabar mensajes de voz.";
|
||||
|
||||
|
||||
@@ -55,9 +55,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"**Add new contact**: to create your one-time QR Code for your contact." = "**Lisää uusi kontakti**: luo kertakäyttöinen QR-koodi tai linkki kontaktille.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Create link / QR code** for your contact to use." = "**Luo linkki / QR-koodi* kontaktille.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**e2e encrypted** audio call" = "**e2e-salattu** äänipuhelu";
|
||||
|
||||
@@ -70,18 +67,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Yksityisin**: älä käytä SimpleX Chat -ilmoituspalvelinta, tarkista viestit ajoittain taustalla (riippuu siitä, kuinka usein käytät sovellusta).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Paste received link** or open it in the browser and tap **Open in mobile app**." = "**Liitä vastaanotettu linkki** tai avaa se selaimessa ja napauta **Avaa mobiilisovelluksessa**.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Huomaa**: et voi palauttaa tai muuttaa tunnuslausetta, jos kadotat sen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Suositus**: laitetunnus ja ilmoitukset lähetetään SimpleX Chat -ilmoituspalvelimelle, mutta ei viestin sisältöä, kokoa tai sitä, keneltä se on peräisin.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Scan QR code**: to connect to your contact in person or via video call." = "**Skannaa QR-koodi**: muodosta yhteys kontaktiisi henkilökohtaisesti tai videopuhelun kautta.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Varoitus**: Välittömät push-ilmoitukset vaativat tunnuslauseen, joka on tallennettu Keychainiin.";
|
||||
|
||||
@@ -244,9 +235,6 @@
|
||||
/* time interval */
|
||||
"1 week" = "1 viikko";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"1-time link" = "Kertakäyttölinkki";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"5 minutes" = "5 minuuttia";
|
||||
|
||||
@@ -384,7 +372,7 @@
|
||||
"Allow disappearing messages only if your contact allows it to you." = "Salli katoavat viestit vain, jos kontaktisi sallii sen sinulle.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow irreversible message deletion only if your contact allows it to you." = "Salli peruuttamaton viestien poisto vain, jos kontaktisi sallii ne sinulle.";
|
||||
"Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Salli peruuttamaton viestien poisto vain, jos kontaktisi sallii ne sinulle. (24 tuntia)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow message reactions only if your contact allows them." = "Salli reaktiot viesteihin vain, jos kontaktisi sallii ne.";
|
||||
@@ -399,7 +387,7 @@
|
||||
"Allow sending disappearing messages." = "Salli katoavien viestien lähettäminen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow to irreversibly delete sent messages." = "Salli lähetettyjen viestien peruuttamaton poistaminen.";
|
||||
"Allow to irreversibly delete sent messages. (24 hours)" = "Salli lähetettyjen viestien peruuttamaton poistaminen. (24 tuntia)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow to send files and media." = "Salli tiedostojen ja median lähettäminen.";
|
||||
@@ -420,7 +408,7 @@
|
||||
"Allow your contacts to call you." = "Salli kontaktiesi soittaa sinulle.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow your contacts to irreversibly delete sent messages." = "Salli kontaktiesi poistaa lähetetyt viestit peruuttamattomasti.";
|
||||
"Allow your contacts to irreversibly delete sent messages. (24 hours)" = "Salli kontaktiesi poistaa lähetetyt viestit peruuttamattomasti. (24 tuntia)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Allow your contacts to send disappearing messages." = "Salli kontaktiesi lähettää katoavia viestejä.";
|
||||
@@ -528,7 +516,7 @@
|
||||
"Both you and your contact can add message reactions." = "Sekä sinä että kontaktisi voivat käyttää viestireaktioita.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Both you and your contact can irreversibly delete sent messages." = "Sekä sinä että kontaktisi voitte peruuttamattomasti poistaa lähetetyt viestit.";
|
||||
"Both you and your contact can irreversibly delete sent messages. (24 hours)" = "Sekä sinä että kontaktisi voitte peruuttamattomasti poistaa lähetetyt viestit. (24 tuntia)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Both you and your contact can make calls." = "Sekä sinä että kontaktisi voitte soittaa puheluita.";
|
||||
@@ -557,9 +545,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Calls" = "Puhelut";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Can't delete user profile!" = "Käyttäjäprofiilia ei voi poistaa!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Can't invite contact!" = "Kontaktia ei voi kutsua!";
|
||||
|
||||
@@ -720,9 +705,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via link" = "Yhdistä linkin kautta";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via link / QR code" = "Yhdistä linkillä / QR-koodilla";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via one-time link" = "Yhdistä kertalinkillä";
|
||||
|
||||
@@ -837,9 +819,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" = "Luo uusi profiili [työpöytäsovelluksessa](https://simplex.chat/downloads/). 💻";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create one-time invitation link" = "Luo kertakutsulinkki";
|
||||
|
||||
/* server test step */
|
||||
"Create queue" = "Luo jono";
|
||||
|
||||
@@ -1603,7 +1582,7 @@
|
||||
"Group members can add message reactions." = "Ryhmän jäsenet voivat lisätä viestireaktioita.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group members can irreversibly delete sent messages." = "Ryhmän jäsenet voivat poistaa lähetetyt viestit peruuttamattomasti.";
|
||||
"Group members can irreversibly delete sent messages. (24 hours)" = "Ryhmän jäsenet voivat poistaa lähetetyt viestit peruuttamattomasti. (24 tuntia)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group members can send direct messages." = "Ryhmän jäsenet voivat lähettää suoraviestejä.";
|
||||
@@ -1695,9 +1674,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"If you can't meet in person, show QR code in a video call, or share the link." = "Jos et voi tavata henkilökohtaisesti, näytä QR-koodi videopuhelussa tai jaa linkki.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link." = "Jos et voi tavata henkilökohtaisesti, voit **skannata QR-koodin videopuhelussa** tai kontaktisi voi jakaa kutsulinkin.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"If you enter this passcode when opening the app, all app data will be irreversibly removed!" = "Jos syötät tämän pääsykoodin sovellusta avatessasi, kaikki sovelluksen tiedot poistetaan peruuttamattomasti!";
|
||||
|
||||
@@ -1938,6 +1914,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Live messages" = "Live-viestit";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Local" = "Paikallinen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Local name" = "Paikallinen nimi";
|
||||
|
||||
@@ -2198,9 +2177,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Off" = "Pois";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Off (Local)" = "Pois (Paikallinen)";
|
||||
|
||||
/* feature offered item */
|
||||
"offered %@" = "tarjottu %@";
|
||||
|
||||
@@ -2247,7 +2223,7 @@
|
||||
"Only you can add message reactions." = "Vain sinä voit lisätä viestireaktioita.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only you can irreversibly delete messages (your contact can mark them for deletion)." = "Vain sinä voit poistaa viestejä peruuttamattomasti (kontaktisi voi merkitä ne poistettavaksi).";
|
||||
"Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)" = "Vain sinä voit poistaa viestejä peruuttamattomasti (kontaktisi voi merkitä ne poistettavaksi). (24 tuntia)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only you can make calls." = "Vain sinä voit soittaa puheluita.";
|
||||
@@ -2262,7 +2238,7 @@
|
||||
"Only your contact can add message reactions." = "Vain kontaktisi voi lisätä viestireaktioita.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only your contact can irreversibly delete messages (you can mark them for deletion)." = "Vain kontaktisi voi poistaa viestejä peruuttamattomasti (voit merkitä ne poistettavaksi).";
|
||||
"Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours)" = "Vain kontaktisi voi poistaa viestejä peruuttamattomasti (voit merkitä ne poistettavaksi). (24 tuntia)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Only your contact can make calls." = "Vain kontaktisi voi soittaa puheluita.";
|
||||
@@ -2288,9 +2264,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Open-source protocol and code – anybody can run the servers." = "Avoimen lähdekoodin protokolla ja koodi - kuka tahansa voi käyttää palvelimia.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Opening database…" = "Avataan tietokantaa…";
|
||||
|
||||
/* member role */
|
||||
"owner" = "omistaja";
|
||||
|
||||
@@ -2312,18 +2285,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Password to show" = "Salasana näytettäväksi";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Paste" = "Liitä";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Paste image" = "Liitä kuva";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Paste received link" = "Liitä vastaanotettu linkki";
|
||||
|
||||
/* placeholder */
|
||||
"Paste the link you received to connect with your contact." = "Liitä saamasi linkki, jonka avulla voit muodostaa yhteyden kontaktiisi.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"peer-to-peer" = "vertais";
|
||||
|
||||
@@ -2867,9 +2831,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Share link" = "Jaa linkki";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share one-time invitation link" = "Jaa kertakutsulinkki";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share with contacts" = "Jaa kontaktien kanssa";
|
||||
|
||||
@@ -2942,9 +2903,6 @@
|
||||
/* notification title */
|
||||
"Somebody" = "Joku";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Start a new chat" = "Aloita uusi keskustelu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Start chat" = "Aloita keskustelu";
|
||||
|
||||
@@ -3104,12 +3062,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Theme" = "Teema";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"There should be at least one user profile." = "Käyttäjäprofiileja tulee olla vähintään yksi.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"There should be at least one visible user profile." = "Näkyviä käyttäjäprofiileja tulee olla vähintään yksi.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"These settings are for your current profile **%@**." = "Nämä asetukset koskevat nykyistä profiiliasi **%@**.";
|
||||
|
||||
@@ -3179,9 +3131,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Turn off" = "Sammuta";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Turn off notifications?" = "Kytke ilmoitukset pois päältä?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Turn on" = "Kytke päälle";
|
||||
|
||||
@@ -3449,9 +3398,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You can accept calls from lock screen, without device and app authentication." = "Voit vastaanottaa puheluita lukitusnäytöltä ilman laitteen ja sovelluksen todennusta.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button." = "Voit myös muodostaa yhteyden klikkaamalla linkkiä. Jos se avautuu selaimessa, napsauta **Avaa mobiilisovelluksessa**-painiketta.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can create it later" = "Voit luoda sen myöhemmin";
|
||||
|
||||
@@ -3602,9 +3548,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Your contacts can allow full message deletion." = "Kontaktisi voivat sallia viestien täydellisen poistamisen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contacts in SimpleX will see it.\nYou can change it in Settings." = "Kontaktisi SimpleX:ssä näkevät sen.\nVoit muuttaa sitä Asetuksista.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contacts will remain connected." = "Kontaktisi pysyvät yhdistettyinä.";
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user