Compare commits
130 Commits
v5.4.2-fdr
...
v5.5.0-fdr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
045b195483 | ||
|
|
283c90f5ae | ||
|
|
99a9fb2e1f | ||
|
|
ce9d583b39 | ||
|
|
53414608db | ||
|
|
c7cf206585 | ||
|
|
6067ac3c93 | ||
|
|
fc56873f1c | ||
|
|
a30da38af7 | ||
|
|
0bf3d054c6 | ||
|
|
a55a8b116a | ||
|
|
7a207fd641 | ||
|
|
4508e0dfc1 | ||
|
|
a853ba3a15 | ||
|
|
a2f190a6c6 | ||
|
|
267178dddb | ||
|
|
fadce0c140 | ||
|
|
58ad97fe6d | ||
|
|
3ccd9903a7 | ||
|
|
e294999044 | ||
|
|
2bbc687f4a | ||
|
|
61c507e7da | ||
|
|
64230f3545 | ||
|
|
bb61b9c658 | ||
|
|
3428f4d2ee | ||
|
|
fe865c5e11 | ||
|
|
9e87fe73a5 | ||
|
|
0ef2c55983 | ||
|
|
8882284fb7 | ||
|
|
767522e701 | ||
|
|
575d899f5a | ||
|
|
d009777901 | ||
|
|
f758a5526a | ||
|
|
e6b5727003 | ||
|
|
c9b1d54f13 | ||
|
|
05065e919b | ||
|
|
5399212e48 | ||
|
|
809040c7bc | ||
|
|
4ab078bd18 | ||
|
|
644169b835 | ||
|
|
78eefee6cc | ||
|
|
e253c55ba4 | ||
|
|
478bb32cdb | ||
|
|
1438fd00e2 | ||
|
|
05b55d3fb5 | ||
|
|
9c061508a4 | ||
|
|
2bacc00a06 | ||
|
|
d637714963 | ||
|
|
5ff6bd15f6 | ||
|
|
5b52d0e173 | ||
|
|
b4dd6941d7 | ||
|
|
07334b057d |
65
.github/workflows/build.yml
vendored
65
.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
|
||||
@@ -81,16 +89,17 @@ jobs:
|
||||
- name: Setup Haskell
|
||||
uses: haskell-actions/setup@v2
|
||||
with:
|
||||
ghc-version: "9.6.3"
|
||||
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
|
||||
@@ -194,7 +203,7 @@ jobs:
|
||||
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
|
||||
@@ -281,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
|
||||
@@ -323,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
|
||||
@@ -350,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 /
|
||||
|
||||
@@ -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/)||
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ 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)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -36,6 +37,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)")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -16,13 +16,13 @@ private var nseSubscribers: [UUID:NSESubscriber] = [:]
|
||||
private let SUSPENDING_TIMEOUT: TimeInterval = 2
|
||||
|
||||
// timeout should be larger than SUSPENDING_TIMEOUT
|
||||
func waitNSESuspended(timeout: TimeInterval, dispatchQueue: DispatchQueue = DispatchQueue.main, suspended: @escaping (Bool) -> Void) {
|
||||
func waitNSESuspended(timeout: TimeInterval, suspended: @escaping (Bool) -> Void) {
|
||||
if timeout <= SUSPENDING_TIMEOUT {
|
||||
logger.warning("waitNSESuspended: small timeout \(timeout), using \(SUSPENDING_TIMEOUT + 1)")
|
||||
}
|
||||
var state = nseStateGroupDefault.get()
|
||||
if case .suspended = state {
|
||||
dispatchQueue.async { suspended(true) }
|
||||
DispatchQueue.main.async { suspended(true) }
|
||||
return
|
||||
}
|
||||
let id = UUID()
|
||||
@@ -45,7 +45,7 @@ func waitNSESuspended(timeout: TimeInterval, dispatchQueue: DispatchQueue = Disp
|
||||
logger.debug("waitNSESuspended notifySuspended: calling suspended(\(ok))")
|
||||
suspendedCalled = true
|
||||
nseSubscribers.removeValue(forKey: id)
|
||||
dispatchQueue.async { suspended(ok) }
|
||||
DispatchQueue.main.async { suspended(ok) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -211,7 +211,7 @@ func apiDeleteUser(_ userId: Int64, _ delSMPQueues: Bool, viewPwd: String?) asyn
|
||||
}
|
||||
|
||||
func apiStartChat() throws -> Bool {
|
||||
let r = chatSendCmdSync(.startChat(subscribe: true, expire: true, xftp: true))
|
||||
let r = chatSendCmdSync(.startChat(mainApp: true))
|
||||
switch r {
|
||||
case .chatStarted: return true
|
||||
case .chatRunning: return false
|
||||
@@ -365,6 +365,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 +380,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 }
|
||||
@@ -403,7 +418,7 @@ func apiGetNtfToken() -> (DeviceToken?, NtfTknStatus?, NotificationsMode) {
|
||||
case let .ntfToken(token, status, ntfMode): return (token, status, ntfMode)
|
||||
case .chatCmdError(_, .errorAgent(.CMD(.PROHIBITED))): return (nil, nil, .off)
|
||||
default:
|
||||
logger.debug("apiGetNtfToken response: \(String(describing: r), privacy: .public)")
|
||||
logger.debug("apiGetNtfToken response: \(String(describing: r))")
|
||||
return (nil, nil, .off)
|
||||
}
|
||||
}
|
||||
@@ -581,15 +596,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 +706,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 +870,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 +1141,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)
|
||||
@@ -1212,9 +1236,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
|
||||
@@ -1231,7 +1257,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 +1345,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 +1359,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 +1375,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 +1548,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 +1686,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 {
|
||||
|
||||
@@ -19,11 +19,13 @@ let terminationTimeout: Int = 3 // seconds
|
||||
|
||||
let activationDelay: TimeInterval = 1.5
|
||||
|
||||
let nseSuspendTimeout: TimeInterval = 5
|
||||
|
||||
private func _suspendChat(timeout: Int) {
|
||||
// this is a redundant check to prevent logical errors, like the one fixed in this PR
|
||||
let state = AppChatState.shared.value
|
||||
if !state.canSuspend {
|
||||
logger.error("_suspendChat called, current state: \(state.rawValue, privacy: .public)")
|
||||
logger.error("_suspendChat called, current state: \(state.rawValue)")
|
||||
} else if ChatModel.ok {
|
||||
AppChatState.shared.set(.suspending)
|
||||
apiSuspendChat(timeoutMicroseconds: timeout * 1000000)
|
||||
@@ -105,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",
|
||||
@@ -134,20 +126,33 @@ func initChatAndMigrate(refreshInvitations: Bool = true) {
|
||||
}
|
||||
}
|
||||
|
||||
func startChatAndActivate(dispatchQueue: DispatchQueue = DispatchQueue.main, _ completion: @escaping () -> Void) {
|
||||
func startChatForCall() {
|
||||
logger.debug("DEBUGGING: startChatForCall")
|
||||
if ChatModel.shared.chatRunning == true {
|
||||
ChatReceiver.shared.start()
|
||||
logger.debug("DEBUGGING: startChatForCall: after ChatReceiver.shared.start")
|
||||
}
|
||||
if .active != AppChatState.shared.value {
|
||||
logger.debug("DEBUGGING: startChatForCall: before activateChat")
|
||||
activateChat()
|
||||
logger.debug("DEBUGGING: startChatForCall: after activateChat")
|
||||
}
|
||||
}
|
||||
|
||||
func startChatAndActivate(_ completion: @escaping () -> Void) {
|
||||
logger.debug("DEBUGGING: startChatAndActivate")
|
||||
if ChatModel.shared.chatRunning == true {
|
||||
ChatReceiver.shared.start()
|
||||
logger.debug("DEBUGGING: startChatAndActivate: after ChatReceiver.shared.start")
|
||||
}
|
||||
if .active == AppChatState.shared.value {
|
||||
if case .active = AppChatState.shared.value {
|
||||
completion()
|
||||
} else if nseStateGroupDefault.get().inactive {
|
||||
activate()
|
||||
} else {
|
||||
// setting app state to "activating" to notify NSE that it should suspend
|
||||
setAppState(.activating)
|
||||
waitNSESuspended(timeout: 10, dispatchQueue: dispatchQueue) { ok in
|
||||
waitNSESuspended(timeout: nseSuspendTimeout) { ok in
|
||||
if !ok {
|
||||
// if for some reason NSE failed to suspend,
|
||||
// e.g., it crashed previously without setting its state to "suspended",
|
||||
|
||||
@@ -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
|
||||
@@ -98,12 +100,12 @@ struct SimpleXApp: App {
|
||||
if legacyDatabase, case .documents = dbContainerGroupDefault.get() {
|
||||
dbContainerGroupDefault.set(.documents)
|
||||
setMigrationState(.offer)
|
||||
logger.debug("SimpleXApp init: using legacy DB in documents folder: \(getAppDatabasePath(), privacy: .public)*.db")
|
||||
logger.debug("SimpleXApp init: using legacy DB in documents folder: \(getAppDatabasePath())*.db")
|
||||
} else {
|
||||
dbContainerGroupDefault.set(.group)
|
||||
setMigrationState(.ready)
|
||||
logger.debug("SimpleXApp init: using DB in app group container: \(getAppDatabasePath(), privacy: .public)*.db")
|
||||
logger.debug("SimpleXApp init: legacy DB\(legacyDatabase ? "" : " not", privacy: .public) present")
|
||||
logger.debug("SimpleXApp init: using DB in app group container: \(getAppDatabasePath())*.db")
|
||||
logger.debug("SimpleXApp init: legacy DB\(legacyDatabase ? "" : " not") present")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,13 +38,13 @@ struct ActiveCallView: View {
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
logger.debug("ActiveCallView: appear client is nil \(client == nil), scenePhase \(String(describing: scenePhase), privacy: .public), canConnectCall \(canConnectCall)")
|
||||
logger.debug("ActiveCallView: appear client is nil \(client == nil), scenePhase \(String(describing: scenePhase)), canConnectCall \(canConnectCall)")
|
||||
AppDelegate.keepScreenOn(true)
|
||||
createWebRTCClient()
|
||||
dismissAllSheets()
|
||||
}
|
||||
.onChange(of: canConnectCall) { _ in
|
||||
logger.debug("ActiveCallView: canConnectCall changed to \(canConnectCall, privacy: .public)")
|
||||
logger.debug("ActiveCallView: canConnectCall changed to \(canConnectCall)")
|
||||
createWebRTCClient()
|
||||
}
|
||||
.onDisappear {
|
||||
|
||||
@@ -130,7 +130,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
|
||||
// The delay allows to accept the second call before suspending a chat
|
||||
// see `.onChange(of: scenePhase)` in SimpleXApp
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in
|
||||
logger.debug("CallController: shouldSuspendChat \(String(describing: self?.shouldSuspendChat), privacy: .public)")
|
||||
logger.debug("CallController: shouldSuspendChat \(String(describing: self?.shouldSuspendChat))")
|
||||
if ChatModel.shared.activeCall == nil && self?.shouldSuspendChat == true {
|
||||
self?.shouldSuspendChat = false
|
||||
suspendChat()
|
||||
@@ -142,45 +142,57 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
|
||||
|
||||
@objc(pushRegistry:didUpdatePushCredentials:forType:)
|
||||
func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
|
||||
logger.debug("CallController: didUpdate push credentials for type \(type.rawValue, privacy: .public)")
|
||||
logger.debug("CallController: didUpdate push credentials for type \(type.rawValue)")
|
||||
}
|
||||
|
||||
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
|
||||
logger.debug("CallController: did receive push with type \(type.rawValue, privacy: .public)")
|
||||
logger.debug("CallController: did receive push with type \(type.rawValue)")
|
||||
if type != .voIP {
|
||||
completion()
|
||||
return
|
||||
}
|
||||
logger.debug("CallController: initializing chat")
|
||||
if (!ChatModel.shared.chatInitialized) {
|
||||
initChatAndMigrate(refreshInvitations: false)
|
||||
if AppChatState.shared.value == .stopped {
|
||||
self.reportExpiredCall(payload: payload, completion)
|
||||
return
|
||||
}
|
||||
startChatAndActivate(dispatchQueue: DispatchQueue.global()) {
|
||||
self.shouldSuspendChat = true
|
||||
// There are no invitations in the model, as it was processed by NSE
|
||||
_ = try? justRefreshCallInvitations()
|
||||
// logger.debug("CallController justRefreshCallInvitations: \(String(describing: m.callInvitations))")
|
||||
// Extract the call information from the push notification payload
|
||||
let m = ChatModel.shared
|
||||
if let contactId = payload.dictionaryPayload["contactId"] as? String,
|
||||
let invitation = m.callInvitations[contactId] {
|
||||
if (!ChatModel.shared.chatInitialized) {
|
||||
logger.debug("CallController: initializing chat")
|
||||
do {
|
||||
try initializeChat(start: true, refreshInvitations: false)
|
||||
} catch let error {
|
||||
logger.error("CallController: initializing chat error: \(error)")
|
||||
self.reportExpiredCall(payload: payload, completion)
|
||||
return
|
||||
}
|
||||
}
|
||||
logger.debug("CallController: initialized chat")
|
||||
startChatForCall()
|
||||
logger.debug("CallController: started chat")
|
||||
self.shouldSuspendChat = true
|
||||
// There are no invitations in the model, as it was processed by NSE
|
||||
_ = try? justRefreshCallInvitations()
|
||||
logger.debug("CallController: updated call invitations chat")
|
||||
// logger.debug("CallController justRefreshCallInvitations: \(String(describing: m.callInvitations))")
|
||||
// Extract the call information from the push notification payload
|
||||
let m = ChatModel.shared
|
||||
if let contactId = payload.dictionaryPayload["contactId"] as? String,
|
||||
let invitation = m.callInvitations[contactId] {
|
||||
let update = self.cxCallUpdate(invitation: invitation)
|
||||
if let uuid = invitation.callkitUUID {
|
||||
logger.debug("CallController: report pushkit call via CallKit")
|
||||
let update = self.cxCallUpdate(invitation: invitation)
|
||||
if let uuid = invitation.callkitUUID {
|
||||
logger.debug("CallController: report pushkit call via CallKit")
|
||||
let update = self.cxCallUpdate(invitation: invitation)
|
||||
self.provider.reportNewIncomingCall(with: uuid, update: update) { error in
|
||||
if error != nil {
|
||||
m.callInvitations.removeValue(forKey: contactId)
|
||||
}
|
||||
// Tell PushKit that the notification is handled.
|
||||
completion()
|
||||
self.provider.reportNewIncomingCall(with: uuid, update: update) { error in
|
||||
if error != nil {
|
||||
m.callInvitations.removeValue(forKey: contactId)
|
||||
}
|
||||
} else {
|
||||
self.reportExpiredCall(update: update, completion)
|
||||
// Tell PushKit that the notification is handled.
|
||||
completion()
|
||||
}
|
||||
} else {
|
||||
self.reportExpiredCall(payload: payload, completion)
|
||||
self.reportExpiredCall(update: update, completion)
|
||||
}
|
||||
} else {
|
||||
self.reportExpiredCall(payload: payload, completion)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +223,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
|
||||
}
|
||||
|
||||
func reportNewIncomingCall(invitation: RcvCallInvitation, completion: @escaping (Error?) -> Void) {
|
||||
logger.debug("CallController.reportNewIncomingCall, UUID=\(String(describing: invitation.callkitUUID), privacy: .public)")
|
||||
logger.debug("CallController.reportNewIncomingCall, UUID=\(String(describing: invitation.callkitUUID))")
|
||||
if CallController.useCallKit(), let uuid = invitation.callkitUUID {
|
||||
if invitation.callTs.timeIntervalSinceNow >= -180 {
|
||||
let update = cxCallUpdate(invitation: invitation)
|
||||
@@ -351,7 +363,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
|
||||
private func requestTransaction(with action: CXAction, onSuccess: @escaping () -> Void = {}) {
|
||||
controller.request(CXTransaction(action: action)) { error in
|
||||
if let error = error {
|
||||
logger.error("CallController.requestTransaction error requesting transaction: \(error.localizedDescription, privacy: .public)")
|
||||
logger.error("CallController.requestTransaction error requesting transaction: \(error.localizedDescription)")
|
||||
} else {
|
||||
logger.debug("CallController.requestTransaction requested transaction successfully")
|
||||
onSuccess()
|
||||
|
||||
@@ -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 {
|
||||
@@ -65,7 +69,8 @@ struct MarkedDeletedItemView: View {
|
||||
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,6 +151,8 @@ struct ChatView: View {
|
||||
)
|
||||
)
|
||||
}
|
||||
} else if case .local = cInfo {
|
||||
ChatInfoToolbar(chat: chat)
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
@@ -205,6 +207,8 @@ struct ChatView: View {
|
||||
Image(systemName: "ellipsis")
|
||||
}
|
||||
}
|
||||
case .local:
|
||||
searchButton()
|
||||
default:
|
||||
EmptyView()
|
||||
}
|
||||
@@ -250,8 +254,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 +268,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 +640,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 +724,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)
|
||||
@@ -748,9 +752,9 @@ struct ChatView: View {
|
||||
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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,8 +53,7 @@ struct GroupWelcomeView: View {
|
||||
}
|
||||
|
||||
private func textPreview() -> some View {
|
||||
messageText(welcomeText, parseSimpleXMarkdown(welcomeText), nil)
|
||||
.allowsHitTesting(false)
|
||||
messageText(welcomeText, parseSimpleXMarkdown(welcomeText), nil, showSecrets: false)
|
||||
.frame(minHeight: 140, alignment: .topLeading)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(" ")
|
||||
@@ -169,7 +173,7 @@ 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 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)
|
||||
|
||||
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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -16,9 +16,11 @@ let logger = Logger()
|
||||
|
||||
let appSuspendingDelay: UInt64 = 2_500_000_000
|
||||
|
||||
let nseSuspendDelay: TimeInterval = 2
|
||||
typealias SuspendSchedule = (delay: TimeInterval, timeout: Int)
|
||||
|
||||
let nseSuspendTimeout: Int = 5
|
||||
let nseSuspendSchedule: SuspendSchedule = (2, 4)
|
||||
|
||||
let fastNSESuspendSchedule: SuspendSchedule = (1, 1)
|
||||
|
||||
typealias NtfStream = ConcurrentQueue<NSENotification>
|
||||
|
||||
@@ -32,7 +34,7 @@ actor PendingNtfs {
|
||||
private var ntfStreams: [String: NtfStream] = [:]
|
||||
|
||||
func createStream(_ id: String) async {
|
||||
logger.debug("NotificationService PendingNtfs.createStream: \(id, privacy: .public)")
|
||||
logger.debug("NotificationService PendingNtfs.createStream: \(id)")
|
||||
if ntfStreams[id] == nil {
|
||||
ntfStreams[id] = ConcurrentQueue()
|
||||
logger.debug("NotificationService PendingNtfs.createStream: created ConcurrentQueue")
|
||||
@@ -40,14 +42,14 @@ actor PendingNtfs {
|
||||
}
|
||||
|
||||
func readStream(_ id: String, for nse: NotificationService, ntfInfo: NtfMessages) async {
|
||||
logger.debug("NotificationService PendingNtfs.readStream: \(id, privacy: .public) \(ntfInfo.ntfMessages.count, privacy: .public)")
|
||||
logger.debug("NotificationService PendingNtfs.readStream: \(id) \(ntfInfo.ntfMessages.count)")
|
||||
if !ntfInfo.user.showNotifications {
|
||||
nse.setBestAttemptNtf(.empty)
|
||||
}
|
||||
if let s = ntfStreams[id] {
|
||||
logger.debug("NotificationService PendingNtfs.readStream: has stream")
|
||||
var expected = Set(ntfInfo.ntfMessages.map { $0.msgId })
|
||||
logger.debug("NotificationService PendingNtfs.readStream: expecting: \(expected, privacy: .public)")
|
||||
logger.debug("NotificationService PendingNtfs.readStream: expecting: \(expected)")
|
||||
var readCancelled = false
|
||||
var dequeued: DequeueElement<NSENotification>?
|
||||
nse.cancelRead = {
|
||||
@@ -66,7 +68,7 @@ actor PendingNtfs {
|
||||
} else if case let .msgInfo(info) = ntf {
|
||||
let found = expected.remove(info.msgId)
|
||||
if found != nil {
|
||||
logger.debug("NotificationService PendingNtfs.readStream: msgInfo, last: \(expected.isEmpty, privacy: .public)")
|
||||
logger.debug("NotificationService PendingNtfs.readStream: msgInfo, last: \(expected.isEmpty)")
|
||||
if expected.isEmpty { break }
|
||||
} else if let msgTs = ntfInfo.msgTs, info.msgTs > msgTs {
|
||||
logger.debug("NotificationService PendingNtfs.readStream: unexpected msgInfo")
|
||||
@@ -88,7 +90,7 @@ actor PendingNtfs {
|
||||
}
|
||||
|
||||
func writeStream(_ id: String, _ ntf: NSENotification) async {
|
||||
logger.debug("NotificationService PendingNtfs.writeStream: \(id, privacy: .public)")
|
||||
logger.debug("NotificationService PendingNtfs.writeStream: \(id)")
|
||||
if let s = ntfStreams[id] {
|
||||
logger.debug("NotificationService PendingNtfs.writeStream: writing ntf")
|
||||
s.enqueue(ntf)
|
||||
@@ -208,7 +210,7 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
self.contentHandler = contentHandler
|
||||
registerGroupDefaults()
|
||||
let appState = appStateGroupDefault.get()
|
||||
logger.debug("NotificationService: app is \(appState.rawValue, privacy: .public)")
|
||||
logger.debug("NotificationService: app is \(appState.rawValue)")
|
||||
switch appState {
|
||||
case .stopped:
|
||||
setBadgeCount()
|
||||
@@ -238,7 +240,7 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.debug("NotificationService: app state is now \(state.rawValue, privacy: .public)")
|
||||
logger.debug("NotificationService: app state is now \(state.rawValue)")
|
||||
if state.inactive {
|
||||
receiveNtfMessages(request, contentHandler)
|
||||
} else {
|
||||
@@ -267,7 +269,7 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
let dbStatus = startChat()
|
||||
if case .ok = dbStatus,
|
||||
let ntfInfo = apiGetNtfMessage(nonce: nonce, encNtfInfo: encNtfInfo) {
|
||||
logger.debug("NotificationService: receiveNtfMessages: apiGetNtfMessage \(String(describing: ntfInfo.ntfMessages.count), privacy: .public)")
|
||||
logger.debug("NotificationService: receiveNtfMessages: apiGetNtfMessage \(String(describing: ntfInfo.ntfMessages.count))")
|
||||
if let connEntity = ntfInfo.connEntity_ {
|
||||
setBestAttemptNtf(
|
||||
ntfInfo.ntfsEnabled
|
||||
@@ -279,7 +281,7 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
NtfStreamSemaphores.shared.waitForStream(id)
|
||||
if receiveEntityId != nil {
|
||||
Task {
|
||||
logger.debug("NotificationService: receiveNtfMessages: in Task, connEntity id \(id, privacy: .public)")
|
||||
logger.debug("NotificationService: receiveNtfMessages: in Task, connEntity id \(id)")
|
||||
await PendingNtfs.shared.createStream(id)
|
||||
await PendingNtfs.shared.readStream(id, for: self, ntfInfo: ntfInfo)
|
||||
deliverBestAttemptNtf()
|
||||
@@ -297,7 +299,7 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
|
||||
override func serviceExtensionTimeWillExpire() {
|
||||
logger.debug("DEBUGGING: NotificationService.serviceExtensionTimeWillExpire")
|
||||
deliverBestAttemptNtf()
|
||||
deliverBestAttemptNtf(urgent: true)
|
||||
}
|
||||
|
||||
func setBadgeCount() {
|
||||
@@ -319,7 +321,7 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
}
|
||||
}
|
||||
|
||||
private func deliverBestAttemptNtf() {
|
||||
private func deliverBestAttemptNtf(urgent: Bool = false) {
|
||||
logger.debug("NotificationService.deliverBestAttemptNtf")
|
||||
if let cancel = cancelRead {
|
||||
cancelRead = nil
|
||||
@@ -329,20 +331,55 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
receiveEntityId = nil
|
||||
NtfStreamSemaphores.shared.signalStreamReady(id)
|
||||
}
|
||||
let suspend: Bool
|
||||
if let t = threadId {
|
||||
threadId = nil
|
||||
if NSEThreads.shared.endThread(t) {
|
||||
logger.debug("NotificationService.deliverBestAttemptNtf: will suspend")
|
||||
// suspension is delayed to allow chat core finalise any processing
|
||||
// (e.g., send delivery receipts)
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + nseSuspendDelay) {
|
||||
if NSEThreads.shared.noThreads {
|
||||
logger.debug("NotificationService.deliverBestAttemptNtf: suspending...")
|
||||
suspendChat(nseSuspendTimeout)
|
||||
suspend = NSEThreads.shared.endThread(t) && NSEThreads.shared.noThreads
|
||||
} else {
|
||||
suspend = false
|
||||
}
|
||||
deliverCallkitOrNotification(urgent: urgent, suspend: suspend)
|
||||
}
|
||||
|
||||
private func deliverCallkitOrNotification(urgent: Bool, suspend: Bool = false) {
|
||||
if case .callkit = bestAttemptNtf {
|
||||
logger.debug("NotificationService.deliverCallkitOrNotification: will suspend, callkit")
|
||||
if urgent {
|
||||
// suspending NSE even though there may be other notifications
|
||||
// to allow the app to process callkit call
|
||||
suspendChat(0)
|
||||
deliverNotification()
|
||||
} else {
|
||||
// suspending NSE with delay and delivering after the suspension
|
||||
// because pushkit notification must be processed without delay
|
||||
// to avoid app termination
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + fastNSESuspendSchedule.delay) {
|
||||
suspendChat(fastNSESuspendSchedule.timeout)
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + Double(fastNSESuspendSchedule.timeout)) {
|
||||
self.deliverNotification()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if suspend {
|
||||
logger.debug("NotificationService.deliverCallkitOrNotification: will suspend")
|
||||
if urgent {
|
||||
suspendChat(0)
|
||||
} else {
|
||||
// suspension is delayed to allow chat core finalise any processing
|
||||
// (e.g., send delivery receipts)
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + nseSuspendSchedule.delay) {
|
||||
if NSEThreads.shared.noThreads {
|
||||
suspendChat(nseSuspendSchedule.timeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
deliverNotification()
|
||||
}
|
||||
}
|
||||
|
||||
private func deliverNotification() {
|
||||
if let handler = contentHandler, let ntf = bestAttemptNtf {
|
||||
contentHandler = nil
|
||||
bestAttemptNtf = nil
|
||||
@@ -357,17 +394,14 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
switch ntf {
|
||||
case let .nse(content): deliver(content)
|
||||
case let .callkit(invitation):
|
||||
logger.debug("NotificationService reportNewIncomingVoIPPushPayload for \(invitation.contact.id)")
|
||||
CXProvider.reportNewIncomingVoIPPushPayload([
|
||||
"displayName": invitation.contact.displayName,
|
||||
"contactId": invitation.contact.id,
|
||||
"media": invitation.callType.media.rawValue
|
||||
]) { error in
|
||||
if error == nil {
|
||||
deliver(nil)
|
||||
} else {
|
||||
logger.debug("NotificationService reportNewIncomingVoIPPushPayload success to CallController for \(invitation.contact.id)")
|
||||
deliver(createCallInvitationNtf(invitation))
|
||||
}
|
||||
logger.debug("reportNewIncomingVoIPPushPayload result: \(error)")
|
||||
deliver(error == nil ? nil : createCallInvitationNtf(invitation))
|
||||
}
|
||||
case .empty: deliver(nil) // used to mute notifications that did not unsubscribe yet
|
||||
case .msgInfo: deliver(nil) // unreachable, the best attempt is never set to msgInfo
|
||||
@@ -402,14 +436,14 @@ var appSubscriber: AppSubscriber = appStateSubscriber { state in
|
||||
logger.debug("NotificationService: appSubscriber")
|
||||
if state.running && NSEChatState.shared.value.canSuspend {
|
||||
logger.debug("NotificationService: appSubscriber app state \(state.rawValue), suspending")
|
||||
suspendChat(nseSuspendTimeout)
|
||||
suspendChat(fastNSESuspendSchedule.timeout)
|
||||
}
|
||||
}
|
||||
|
||||
func appStateSubscriber(onState: @escaping (AppState) -> Void) -> AppSubscriber {
|
||||
appMessageSubscriber { msg in
|
||||
if case let .state(state) = msg {
|
||||
logger.debug("NotificationService: appStateSubscriber \(state.rawValue, privacy: .public)")
|
||||
logger.debug("NotificationService: appStateSubscriber \(state.rawValue)")
|
||||
onState(state)
|
||||
}
|
||||
}
|
||||
@@ -425,24 +459,33 @@ let xftpConfig: XFTPFileConfig? = getXFTPCfg()
|
||||
// Subsequent calls to didReceive will be waiting on semaphore and won't start chat again, as it will be .active
|
||||
func startChat() -> DBMigrationResult? {
|
||||
logger.debug("NotificationService: startChat")
|
||||
if case .active = NSEChatState.shared.value { return .ok }
|
||||
// only skip creating if there is chat controller
|
||||
if case .active = NSEChatState.shared.value, hasChatCtrl() { return .ok }
|
||||
|
||||
startLock.wait()
|
||||
defer { startLock.signal() }
|
||||
|
||||
return switch NSEChatState.shared.value {
|
||||
case .created: doStartChat()
|
||||
case .starting: .ok // it should never get to this branch, as it would be waiting for start on startLock
|
||||
case .active: .ok
|
||||
case .suspending: activateChat()
|
||||
case .suspended: activateChat()
|
||||
if hasChatCtrl() {
|
||||
return switch NSEChatState.shared.value {
|
||||
case .created: doStartChat()
|
||||
case .starting: .ok // it should never get to this branch, as it would be waiting for start on startLock
|
||||
case .active: .ok
|
||||
case .suspending: activateChat()
|
||||
case .suspended: activateChat()
|
||||
}
|
||||
} else {
|
||||
// Ignore state in preference if there is no chat controller.
|
||||
// State in preference may have failed to update e.g. because of a crash.
|
||||
NSEChatState.shared.set(.created)
|
||||
return doStartChat()
|
||||
}
|
||||
}
|
||||
|
||||
func doStartChat() -> DBMigrationResult? {
|
||||
logger.debug("NotificationService: doStartChat")
|
||||
hs_init(0, nil)
|
||||
haskell_init_nse()
|
||||
let (_, dbStatus) = chatMigrateInit(confirmMigrations: defaultMigrationConfirmation(), backgroundMode: true)
|
||||
logger.debug("NotificationService: doStartChat \(String(describing: dbStatus))")
|
||||
if dbStatus != .ok {
|
||||
resetChatCtrl()
|
||||
NSEChatState.shared.set(.created)
|
||||
@@ -477,7 +520,7 @@ func doStartChat() -> DBMigrationResult? {
|
||||
return .ok
|
||||
}
|
||||
} catch {
|
||||
logger.error("NotificationService startChat error: \(responseError(error), privacy: .public)")
|
||||
logger.error("NotificationService startChat error: \(responseError(error))")
|
||||
}
|
||||
} else {
|
||||
logger.debug("NotificationService: no active user")
|
||||
@@ -504,8 +547,10 @@ func suspendChat(_ timeout: Int) {
|
||||
logger.debug("NotificationService: suspendChat")
|
||||
let state = NSEChatState.shared.value
|
||||
if !state.canSuspend {
|
||||
logger.error("NotificationService suspendChat called, current state: \(state.rawValue, privacy: .public)")
|
||||
} else {
|
||||
logger.error("NotificationService suspendChat called, current state: \(state.rawValue)")
|
||||
} else if hasChatCtrl() {
|
||||
// only suspend if we have chat controller to avoid crashes when suspension is
|
||||
// attempted when chat controller was not created
|
||||
suspendLock.wait()
|
||||
defer { suspendLock.signal() }
|
||||
|
||||
@@ -571,7 +616,7 @@ private let isInChina = SKStorefront().countryCode == "CHN"
|
||||
private func useCallKit() -> Bool { !isInChina && callKitEnabledGroupDefault.get() }
|
||||
|
||||
func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotification)? {
|
||||
logger.debug("NotificationService receivedMsgNtf: \(res.responseType, privacy: .public)")
|
||||
logger.debug("NotificationService receivedMsgNtf: \(res.responseType)")
|
||||
switch res {
|
||||
case let .contactConnected(user, contact, _):
|
||||
return (contact.id, .nse(createContactConnectedNtf(user, contact)))
|
||||
@@ -586,7 +631,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
|
||||
@@ -613,6 +658,9 @@ func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotification)? {
|
||||
case .chatSuspended:
|
||||
chatSuspended()
|
||||
return nil
|
||||
case let .chatError(_, err):
|
||||
logger.error("NotificationService receivedMsgNtf error: \(String(describing: err))")
|
||||
return nil
|
||||
default:
|
||||
logger.debug("NotificationService receivedMsgNtf ignored event: \(res.responseType)")
|
||||
return nil
|
||||
@@ -627,17 +675,22 @@ func updateNetCfg() {
|
||||
try setNetworkConfig(networkConfig)
|
||||
networkConfig = newNetConfig
|
||||
} catch {
|
||||
logger.error("NotificationService apply changed network config error: \(responseError(error), privacy: .public)")
|
||||
logger.error("NotificationService apply changed network config error: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func apiGetActiveUser() -> User? {
|
||||
let r = sendSimpleXCmd(.showActiveUser)
|
||||
logger.debug("apiGetActiveUser sendSimpleXCmd response: \(String(describing: r))")
|
||||
logger.debug("apiGetActiveUser sendSimpleXCmd response: \(r.responseType)")
|
||||
switch r {
|
||||
case let .activeUser(user): return user
|
||||
case .chatCmdError(_, .error(.noActiveUser)): return nil
|
||||
case .chatCmdError(_, .error(.noActiveUser)):
|
||||
logger.debug("apiGetActiveUser sendSimpleXCmd no active user")
|
||||
return nil
|
||||
case let .chatCmdError(_, err):
|
||||
logger.debug("apiGetActiveUser sendSimpleXCmd error: \(String(describing: err))")
|
||||
return nil
|
||||
default:
|
||||
logger.error("NotificationService apiGetActiveUser unexpected response: \(String(describing: r))")
|
||||
return nil
|
||||
@@ -645,7 +698,7 @@ func apiGetActiveUser() -> User? {
|
||||
}
|
||||
|
||||
func apiStartChat() throws -> Bool {
|
||||
let r = sendSimpleXCmd(.startChat(subscribe: false, expire: false, xftp: false))
|
||||
let r = sendSimpleXCmd(.startChat(mainApp: false))
|
||||
switch r {
|
||||
case .chatStarted: return true
|
||||
case .chatRunning: return false
|
||||
@@ -699,11 +752,12 @@ func apiGetNtfMessage(nonce: String, encNtfInfo: String) -> NtfMessages? {
|
||||
}
|
||||
let r = sendSimpleXCmd(.apiGetNtfMessage(nonce: nonce, encNtfInfo: encNtfInfo))
|
||||
if case let .ntfMessages(user, connEntity_, msgTs, ntfMessages) = r, let user = user {
|
||||
logger.debug("apiGetNtfMessage response ntfMessages: \(ntfMessages.count)")
|
||||
return NtfMessages(user: user, connEntity_: connEntity_, msgTs: msgTs, ntfMessages: ntfMessages)
|
||||
} else if case let .chatCmdError(_, error) = r {
|
||||
logger.debug("apiGetNtfMessage error response: \(String.init(describing: error))")
|
||||
} else {
|
||||
logger.debug("apiGetNtfMessage ignored response: \(r.responseType, privacy: .public) \(String.init(describing: r), privacy: .private)")
|
||||
logger.debug("apiGetNtfMessage ignored response: \(r.responseType) \(String.init(describing: r))")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -721,13 +775,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 */; };
|
||||
@@ -43,11 +42,6 @@
|
||||
5C3F1D562842B68D00EC8A82 /* IntegrityErrorItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3F1D552842B68D00EC8A82 /* IntegrityErrorItemView.swift */; };
|
||||
5C3F1D58284363C400EC8A82 /* PrivacySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */; };
|
||||
5C4B3B0A285FB130003915F2 /* DatabaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B3B09285FB130003915F2 /* DatabaseView.swift */; };
|
||||
5C4E80DA2B3CCD090080FAE2 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80D52B3CCD090080FAE2 /* libgmp.a */; };
|
||||
5C4E80DB2B3CCD090080FAE2 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80D62B3CCD090080FAE2 /* libffi.a */; };
|
||||
5C4E80DC2B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80D72B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a */; };
|
||||
5C4E80DD2B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80D82B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a */; };
|
||||
5C4E80DE2B3CCD090080FAE2 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80D92B3CCD090080FAE2 /* libgmpxx.a */; };
|
||||
5C5346A827B59A6A004DF848 /* ChatHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5346A727B59A6A004DF848 /* ChatHelp.swift */; };
|
||||
5C55A91F283AD0E400C4E99E /* CallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C55A91E283AD0E400C4E99E /* CallManager.swift */; };
|
||||
5C55A921283CCCB700C4E99E /* IncomingCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C55A920283CCCB700C4E99E /* IncomingCallView.swift */; };
|
||||
@@ -61,13 +55,17 @@
|
||||
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 */; };
|
||||
5C7505A527B679EE00BE3227 /* NavLinkPlain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7505A427B679EE00BE3227 /* NavLinkPlain.swift */; };
|
||||
5C7505A827B6D34800BE3227 /* ChatInfoToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7505A727B6D34800BE3227 /* ChatInfoToolbar.swift */; };
|
||||
5C764E89279CBCB3000C6508 /* ChatModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C764E88279CBCB3000C6508 /* ChatModel.swift */; };
|
||||
5C83A1AD2B5EF67D00AE0A4A /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C83A1A82B5EF67D00AE0A4A /* libgmp.a */; };
|
||||
5C83A1AE2B5EF67D00AE0A4A /* libHSsimplex-chat-5.5.0.4-HTW6wkBBAjO2GDtnvnI9O3-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C83A1A92B5EF67D00AE0A4A /* libHSsimplex-chat-5.5.0.4-HTW6wkBBAjO2GDtnvnI9O3-ghc9.6.3.a */; };
|
||||
5C83A1AF2B5EF67D00AE0A4A /* libHSsimplex-chat-5.5.0.4-HTW6wkBBAjO2GDtnvnI9O3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C83A1AA2B5EF67D00AE0A4A /* libHSsimplex-chat-5.5.0.4-HTW6wkBBAjO2GDtnvnI9O3.a */; };
|
||||
5C83A1B02B5EF67D00AE0A4A /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C83A1AB2B5EF67D00AE0A4A /* libffi.a */; };
|
||||
5C83A1B12B5EF67D00AE0A4A /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C83A1AC2B5EF67D00AE0A4A /* libgmpxx.a */; };
|
||||
5C8F01CD27A6F0D8007D2C8D /* CodeScanner in Frameworks */ = {isa = PBXBuildFile; productRef = 5C8F01CC27A6F0D8007D2C8D /* CodeScanner */; };
|
||||
5C93292F29239A170090FFF9 /* ProtocolServersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C93292E29239A170090FFF9 /* ProtocolServersView.swift */; };
|
||||
5C93293129239BED0090FFF9 /* ProtocolServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C93293029239BED0090FFF9 /* ProtocolServerView.swift */; };
|
||||
@@ -98,8 +96,6 @@
|
||||
5CB0BA92282713FD00B3292C /* CreateProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB0BA91282713FD00B3292C /* CreateProfile.swift */; };
|
||||
5CB0BA9A2827FD8800B3292C /* HowItWorks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB0BA992827FD8800B3292C /* HowItWorks.swift */; };
|
||||
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 */; };
|
||||
@@ -263,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>"; };
|
||||
@@ -280,6 +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>"; };
|
||||
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>"; };
|
||||
@@ -294,11 +292,6 @@
|
||||
5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacySettings.swift; sourceTree = "<group>"; };
|
||||
5C422A7C27A9A6FA0097A1E1 /* SimpleX (iOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "SimpleX (iOS).entitlements"; sourceTree = "<group>"; };
|
||||
5C4B3B09285FB130003915F2 /* DatabaseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseView.swift; sourceTree = "<group>"; };
|
||||
5C4E80D52B3CCD090080FAE2 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5C4E80D62B3CCD090080FAE2 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5C4E80D72B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a"; sourceTree = "<group>"; };
|
||||
5C4E80D82B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
5C4E80D92B3CCD090080FAE2 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5C5346A727B59A6A004DF848 /* ChatHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHelp.swift; sourceTree = "<group>"; };
|
||||
5C55A91E283AD0E400C4E99E /* CallManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallManager.swift; sourceTree = "<group>"; };
|
||||
5C55A920283CCCB700C4E99E /* IncomingCallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomingCallView.swift; sourceTree = "<group>"; };
|
||||
@@ -324,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>"; };
|
||||
@@ -333,6 +325,11 @@
|
||||
5C7505A427B679EE00BE3227 /* NavLinkPlain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavLinkPlain.swift; sourceTree = "<group>"; };
|
||||
5C7505A727B6D34800BE3227 /* ChatInfoToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatInfoToolbar.swift; sourceTree = "<group>"; };
|
||||
5C764E88279CBCB3000C6508 /* ChatModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatModel.swift; sourceTree = "<group>"; };
|
||||
5C83A1A82B5EF67D00AE0A4A /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5C83A1A92B5EF67D00AE0A4A /* libHSsimplex-chat-5.5.0.4-HTW6wkBBAjO2GDtnvnI9O3-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.5.0.4-HTW6wkBBAjO2GDtnvnI9O3-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
5C83A1AA2B5EF67D00AE0A4A /* libHSsimplex-chat-5.5.0.4-HTW6wkBBAjO2GDtnvnI9O3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.5.0.4-HTW6wkBBAjO2GDtnvnI9O3.a"; sourceTree = "<group>"; };
|
||||
5C83A1AB2B5EF67D00AE0A4A /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5C83A1AC2B5EF67D00AE0A4A /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5C84FE9129A216C800D95B1A /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
5C84FE9329A2179C00D95B1A /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = "nl.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
5C84FE9429A2179C00D95B1A /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
@@ -381,8 +378,6 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -408,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; };
|
||||
@@ -444,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>"; };
|
||||
@@ -519,13 +514,13 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5C4E80DD2B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a in Frameworks */,
|
||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||
5C4E80DA2B3CCD090080FAE2 /* libgmp.a in Frameworks */,
|
||||
5C4E80DC2B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a in Frameworks */,
|
||||
5C83A1B02B5EF67D00AE0A4A /* libffi.a in Frameworks */,
|
||||
5C83A1AD2B5EF67D00AE0A4A /* libgmp.a in Frameworks */,
|
||||
5C83A1AE2B5EF67D00AE0A4A /* libHSsimplex-chat-5.5.0.4-HTW6wkBBAjO2GDtnvnI9O3-ghc9.6.3.a in Frameworks */,
|
||||
5C83A1AF2B5EF67D00AE0A4A /* libHSsimplex-chat-5.5.0.4-HTW6wkBBAjO2GDtnvnI9O3.a in Frameworks */,
|
||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||
5C4E80DB2B3CCD090080FAE2 /* libffi.a in Frameworks */,
|
||||
5C4E80DE2B3CCD090080FAE2 /* libgmpxx.a in Frameworks */,
|
||||
5C83A1B12B5EF67D00AE0A4A /* libgmpxx.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -587,11 +582,11 @@
|
||||
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5C4E80D62B3CCD090080FAE2 /* libffi.a */,
|
||||
5C4E80D52B3CCD090080FAE2 /* libgmp.a */,
|
||||
5C4E80D92B3CCD090080FAE2 /* libgmpxx.a */,
|
||||
5C4E80D82B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a */,
|
||||
5C4E80D72B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a */,
|
||||
5C83A1AB2B5EF67D00AE0A4A /* libffi.a */,
|
||||
5C83A1A82B5EF67D00AE0A4A /* libgmp.a */,
|
||||
5C83A1AC2B5EF67D00AE0A4A /* libgmpxx.a */,
|
||||
5C83A1A92B5EF67D00AE0A4A /* libHSsimplex-chat-5.5.0.4-HTW6wkBBAjO2GDtnvnI9O3-ghc9.6.3.a */,
|
||||
5C83A1AA2B5EF67D00AE0A4A /* libHSsimplex-chat-5.5.0.4-HTW6wkBBAjO2GDtnvnI9O3.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
@@ -737,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;
|
||||
@@ -1051,6 +1042,7 @@
|
||||
fi,
|
||||
uk,
|
||||
bg,
|
||||
tr,
|
||||
);
|
||||
mainGroup = 5CA059BD279559F40002BEB4;
|
||||
packageReferences = (
|
||||
@@ -1113,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 */,
|
||||
@@ -1161,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 */,
|
||||
@@ -1176,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 */,
|
||||
@@ -1212,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 */,
|
||||
@@ -1235,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 */,
|
||||
@@ -1342,6 +1330,7 @@
|
||||
5C136D8F2AAB3D14006DE2FC /* fi */,
|
||||
5C636F672AAB3D2400751C84 /* uk */,
|
||||
5C5B67932ABAF56000DA9412 /* bg */,
|
||||
5C245F3E2B501F13001CC39F /* tr */,
|
||||
);
|
||||
name = InfoPlist.strings;
|
||||
sourceTree = "<group>";
|
||||
@@ -1364,6 +1353,7 @@
|
||||
5CE6C7B32AAB1515007F345C /* fi */,
|
||||
5CE6C7B42AAB1527007F345C /* uk */,
|
||||
5C5B67912ABAF4B500DA9412 /* bg */,
|
||||
5C245F3C2B501E98001CC39F /* tr */,
|
||||
);
|
||||
name = Localizable.strings;
|
||||
sourceTree = "<group>";
|
||||
@@ -1385,6 +1375,7 @@
|
||||
5C136D8E2AAB3D14006DE2FC /* fi */,
|
||||
5C636F662AAB3D2400751C84 /* uk */,
|
||||
5C5B67922ABAF56000DA9412 /* bg */,
|
||||
5C245F3D2B501F13001CC39F /* tr */,
|
||||
);
|
||||
name = "SimpleX--iOS--InfoPlist.strings";
|
||||
sourceTree = "<group>";
|
||||
@@ -1518,7 +1509,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 186;
|
||||
CURRENT_PROJECT_VERSION = 194;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1540,7 +1531,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.4.2;
|
||||
MARKETING_VERSION = 5.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1561,7 +1552,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 186;
|
||||
CURRENT_PROJECT_VERSION = 194;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1583,7 +1574,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.4.2;
|
||||
MARKETING_VERSION = 5.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1642,7 +1633,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 186;
|
||||
CURRENT_PROJECT_VERSION = 194;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -1655,7 +1646,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.4.2;
|
||||
MARKETING_VERSION = 5.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -1674,7 +1665,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 186;
|
||||
CURRENT_PROJECT_VERSION = 194;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -1687,7 +1678,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.4.2;
|
||||
MARKETING_VERSION = 5.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -1706,7 +1697,7 @@
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 186;
|
||||
CURRENT_PROJECT_VERSION = 194;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1730,7 +1721,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Libraries/sim",
|
||||
);
|
||||
MARKETING_VERSION = 5.4.2;
|
||||
MARKETING_VERSION = 5.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1752,7 +1743,7 @@
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 186;
|
||||
CURRENT_PROJECT_VERSION = 194;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1776,7 +1767,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Libraries/sim",
|
||||
);
|
||||
MARKETING_VERSION = 5.4.2;
|
||||
MARKETING_VERSION = 5.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Release"
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1400"
|
||||
wasCreatedForAppExtension = "YES"
|
||||
version = "2.0">
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
@@ -47,16 +47,14 @@
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = ""
|
||||
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
askForAppToLaunch = "Yes"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES"
|
||||
launchAutomaticallySubstyle = "2">
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
|
||||
@@ -12,7 +12,11 @@ private var chatController: chat_ctrl?
|
||||
|
||||
private var migrationResult: (Bool, DBMigrationResult)?
|
||||
|
||||
public func getChatCtrl(_ useKey: String? = nil) -> chat_ctrl {
|
||||
public func hasChatCtrl() -> Bool {
|
||||
chatController != nil
|
||||
}
|
||||
|
||||
public func getChatCtrl() -> chat_ctrl {
|
||||
if let controller = chatController { return controller }
|
||||
fatalError("chat controller not initialized")
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ public enum ChatCommand {
|
||||
case apiMuteUser(userId: Int64)
|
||||
case apiUnmuteUser(userId: Int64)
|
||||
case apiDeleteUser(userId: Int64, delSMPQueues: Bool, viewPwd: String?)
|
||||
case startChat(subscribe: Bool, expire: Bool, xftp: Bool)
|
||||
case startChat(mainApp: Bool)
|
||||
case apiStopChat
|
||||
case apiActivateChat(restoreChat: Bool)
|
||||
case apiSuspendChat(timeoutMicroseconds: Int)
|
||||
@@ -41,6 +41,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 +55,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)
|
||||
@@ -154,7 +156,7 @@ public enum ChatCommand {
|
||||
case let .apiMuteUser(userId): return "/_mute user \(userId)"
|
||||
case let .apiUnmuteUser(userId): return "/_unmute user \(userId)"
|
||||
case let .apiDeleteUser(userId, delSMPQueues, viewPwd): return "/_delete user \(userId) del_smp=\(onOff(delSMPQueues))\(maybePwd(viewPwd))"
|
||||
case let .startChat(subscribe, expire, xftp): return "/_start subscribe=\(onOff(subscribe)) expire=\(onOff(expire)) xftp=\(onOff(xftp))"
|
||||
case let .startChat(mainApp): return "/_start main=\(onOff(mainApp))"
|
||||
case .apiStopChat: return "/_stop"
|
||||
case let .apiActivateChat(restore): return "/_app activate restore=\(onOff(restore))"
|
||||
case let .apiSuspendChat(timeoutMicroseconds): return "/_app suspend \(timeoutMicroseconds)"
|
||||
@@ -178,6 +180,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 +196,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)"
|
||||
@@ -315,6 +321,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 +336,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 +569,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 +721,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 +871,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)")
|
||||
@@ -1610,6 +1624,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)
|
||||
|
||||
@@ -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,7 @@ 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
|
||||
@@ -1326,6 +1370,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 +1378,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())
|
||||
)
|
||||
@@ -1768,6 +1814,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 +1826,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 +1849,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 +1886,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 +1901,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 +1910,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 +1928,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 +1998,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 +2013,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 +2030,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 +2043,37 @@ 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
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
public enum InvitedBy: Decodable {
|
||||
case contact(byContactId: Int64)
|
||||
case user
|
||||
@@ -2092,6 +2193,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 +2238,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 +2310,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 +2493,8 @@ public enum CIDirection: Decodable {
|
||||
case directRcv
|
||||
case groupSnd
|
||||
case groupRcv(groupMember: GroupMember)
|
||||
case localSnd
|
||||
case localRcv
|
||||
|
||||
public var sent: Bool {
|
||||
get {
|
||||
@@ -2389,6 +2503,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 +2671,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 +2719,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 +2750,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 +2789,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 +2835,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 +2996,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 +3077,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 +3265,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 +3282,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 +3444,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 +3476,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 +3484,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 +3494,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 +3509,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 +3530,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 +3541,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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,3 +23,19 @@ void haskell_init(void) {
|
||||
char **pargv = argv;
|
||||
hs_init_with_rtsopts(&argc, &pargv);
|
||||
}
|
||||
|
||||
void haskell_init_nse(void) {
|
||||
int argc = 7;
|
||||
char *argv[] = {
|
||||
"simplex",
|
||||
"+RTS", // requires `hs_init_with_rtsopts`
|
||||
"-A1m", // chunk size for new allocations
|
||||
"-H1m", // initial heap size
|
||||
"-F0.5", // heap growth triggering GC
|
||||
"-Fd1", // memory return
|
||||
"-c", // compacting garbage collector
|
||||
0
|
||||
};
|
||||
char **pargv = argv;
|
||||
hs_init_with_rtsopts(&argc, &pargv);
|
||||
}
|
||||
|
||||
@@ -11,4 +11,6 @@
|
||||
|
||||
void haskell_init(void);
|
||||
|
||||
void haskell_init_nse(void);
|
||||
|
||||
#endif /* hs_init_h */
|
||||
|
||||
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";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user