Compare commits
339 Commits
v5.4.0-bet
...
v5.4.0-fdr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc8ca4cf89 | ||
|
|
1902b692f5 | ||
|
|
6c05eb0ff3 | ||
|
|
d148ce4cbb | ||
|
|
3d09073bfc | ||
|
|
da64b2e3cd | ||
|
|
4572fec61d | ||
|
|
b62dd801f1 | ||
|
|
fe9953fc49 | ||
|
|
e91a1f151d | ||
|
|
0c096e2c89 | ||
|
|
34d7fe3744 | ||
|
|
4327b023ed | ||
|
|
50bada24af | ||
|
|
97934c8289 | ||
|
|
4a254560c0 | ||
|
|
f9b5c673c5 | ||
|
|
8ce9dd7ab6 | ||
|
|
9fb4b3cf40 | ||
|
|
7f9a490edb | ||
|
|
bfd13f059a | ||
|
|
c9aec88c39 | ||
|
|
b1cf1656a0 | ||
|
|
74e80eb348 | ||
|
|
6f3174d0a1 | ||
|
|
8f0a9cd609 | ||
|
|
b2dbb558f9 | ||
|
|
f7903c5c83 | ||
|
|
4d3529a3e0 | ||
|
|
cc127e56fe | ||
|
|
1781495ee3 | ||
|
|
d837f87f09 | ||
|
|
d3f9616f9b | ||
|
|
1b7baa244a | ||
|
|
954b7150af | ||
|
|
d0419df396 | ||
|
|
01f351e65a | ||
|
|
2d4e99d610 | ||
|
|
4af4fbae2b | ||
|
|
15fdab597b | ||
|
|
0c1d78ab08 | ||
|
|
324f614e00 | ||
|
|
cec0fe2702 | ||
|
|
48d7afc959 | ||
|
|
9442121efa | ||
|
|
69acac5331 | ||
|
|
aade3d359f | ||
|
|
c1d89f2c0f | ||
|
|
c40bfb0f43 | ||
|
|
831231d8e6 | ||
|
|
45102442f4 | ||
|
|
64520a4cf4 | ||
|
|
d0f43628ef | ||
|
|
febf3e0a45 | ||
|
|
097242e7a8 | ||
|
|
f323c8e112 | ||
|
|
3bdc6b5e28 | ||
|
|
a8576c2340 | ||
|
|
5a08a26c9a | ||
|
|
da8789ef4c | ||
|
|
d8373262bc | ||
|
|
3597d34716 | ||
|
|
47cd7de1ae | ||
|
|
624a3abba2 | ||
|
|
121985138a | ||
|
|
7f5efd8927 | ||
|
|
96d3c9988c | ||
|
|
f6b786a187 | ||
|
|
11478da6ef | ||
|
|
72654caca6 | ||
|
|
bd4259e89e | ||
|
|
55ead740cc | ||
|
|
5ef0eda2d7 | ||
|
|
49a9b0e7d6 | ||
|
|
45ada450a2 | ||
|
|
307a1b3c5e | ||
|
|
ed6b3bbead | ||
|
|
44c88badde | ||
|
|
53a31ec60e | ||
|
|
718436bf55 | ||
|
|
5bbf4d70a1 | ||
|
|
970ca3a409 | ||
|
|
c536ca7f0f | ||
|
|
07ef2a0b64 | ||
|
|
5b7de8f8c1 | ||
|
|
68cbc605be | ||
|
|
901610eec5 | ||
|
|
7d4127c51d | ||
|
|
3a510eeaf0 | ||
|
|
ba94f76a90 | ||
|
|
85e44dcb77 | ||
|
|
13215d91d7 | ||
|
|
e1a8099474 | ||
|
|
daa8d9bb21 | ||
|
|
2a8d7b8926 | ||
|
|
d9031cb209 | ||
|
|
bf8457fb40 | ||
|
|
59392b361b | ||
|
|
8f0538e756 | ||
|
|
b164cc2fa6 | ||
|
|
f9e5a56e1a | ||
|
|
96e000e3ea | ||
|
|
ca8833c0c1 | ||
|
|
e95d9d0b49 | ||
|
|
cc434cda55 | ||
|
|
c0e8740f50 | ||
|
|
80abc18371 | ||
|
|
c9a1de6e4b | ||
|
|
42e0400014 | ||
|
|
79064e149a | ||
|
|
84e09f195c | ||
|
|
f6c4e969e4 | ||
|
|
86b916c169 | ||
|
|
5fcbade1bc | ||
|
|
3937ffa9a6 | ||
|
|
cf102da4d3 | ||
|
|
0d7a048988 | ||
|
|
d0f3a3d886 | ||
|
|
64f0dbeb61 | ||
|
|
80ddb50e1c | ||
|
|
f6e66f1c53 | ||
|
|
0322b9708b | ||
|
|
c31ae39617 | ||
|
|
339c3d2be1 | ||
|
|
a75fce8dfa | ||
|
|
b71daed3ec | ||
|
|
fa9d61caa4 | ||
|
|
975f6d488e | ||
|
|
3d617bce25 | ||
|
|
d4ba1bbe69 | ||
|
|
0a4920daae | ||
|
|
36509a6d79 | ||
|
|
4da1d21c81 | ||
|
|
5bbde22ffa | ||
|
|
1e8ae6d861 | ||
|
|
f9df5aa41b | ||
|
|
c91625b32a | ||
|
|
598b6659cc | ||
|
|
a2fe5cfb66 | ||
|
|
86bc70fa5a | ||
|
|
338417d963 | ||
|
|
72b25385ba | ||
|
|
92e3f576ca | ||
|
|
5beeff5cb6 | ||
|
|
8e3e58cac8 | ||
|
|
8b67ff7a00 | ||
|
|
0c23ff9ae3 | ||
|
|
1570bc2b99 | ||
|
|
beb22c6f87 | ||
|
|
11362941fd | ||
|
|
1e2104cabf | ||
|
|
f3014f258d | ||
|
|
b1101fbce4 | ||
|
|
f7b4e4b16a | ||
|
|
97fd6a993e | ||
|
|
f0991cc0ba | ||
|
|
74b78a8d7b | ||
|
|
82cd70a75c | ||
|
|
fe4eb7b5af | ||
|
|
83aaaa9ada | ||
|
|
ae286124aa | ||
|
|
9cc232054c | ||
|
|
227007c8f6 | ||
|
|
e17e6adefb | ||
|
|
02225df274 | ||
|
|
e7d6ed66da | ||
|
|
8d891005d9 | ||
|
|
f648086934 | ||
|
|
fcdd8ce7c1 | ||
|
|
c0be36737d | ||
|
|
f49ded5ae5 | ||
|
|
f41861c026 | ||
|
|
6d4febb669 | ||
|
|
3dd62ab05a | ||
|
|
96d94d3438 | ||
|
|
b729144773 | ||
|
|
3839267f88 | ||
|
|
d233d07ddc | ||
|
|
8722d35278 | ||
|
|
ee6bd0f839 | ||
|
|
e3938f6fb5 | ||
|
|
2dc621a56c | ||
|
|
3e46c5dfaf | ||
|
|
a04dc5d05b | ||
|
|
2776d864a8 | ||
|
|
b33fe01e49 | ||
|
|
c459e71d02 | ||
|
|
2516d5a393 | ||
|
|
15b55f7924 | ||
|
|
477d98d75a | ||
|
|
4253cd7fb9 | ||
|
|
177112ab18 | ||
|
|
c2a99987f3 | ||
|
|
eee233bd02 | ||
|
|
10cbb13c26 | ||
|
|
ca78958667 | ||
|
|
1f5b80d560 | ||
|
|
2de111e76c | ||
|
|
4816150b99 | ||
|
|
3d7258fa58 | ||
|
|
c462dd3704 | ||
|
|
0cc26d192d | ||
|
|
8546c937b2 | ||
|
|
34b07d6a3b | ||
|
|
fad5128a83 | ||
|
|
8482dbfd99 | ||
|
|
4fd38a270c | ||
|
|
b2f9270452 | ||
|
|
4cc20a2d32 | ||
|
|
68873464d7 | ||
|
|
c1a0486c1d | ||
|
|
c8c17a2f68 | ||
|
|
02c0cd5619 | ||
|
|
9e8084874f | ||
|
|
07173f7b2f | ||
|
|
42458a2715 | ||
|
|
8343285d93 | ||
|
|
5dbe2b2745 | ||
|
|
b1fdc936a6 | ||
|
|
f34bbdbd9c | ||
|
|
be44632b0b | ||
|
|
b48690dee6 | ||
|
|
d90da57f12 | ||
|
|
fb9485190d | ||
|
|
6881600e06 | ||
|
|
9568279b0f | ||
|
|
9fb2b7fe73 | ||
|
|
a7b5dfb74c | ||
|
|
7102723c23 | ||
|
|
16bda26022 | ||
|
|
4a5fdd3e0e | ||
|
|
3790752378 | ||
|
|
cd98fabe43 | ||
|
|
4a8da196ad | ||
|
|
743597e848 | ||
|
|
9ed723bafa | ||
|
|
9ded1c9821 | ||
|
|
b0f55d6de5 | ||
|
|
6185971827 | ||
|
|
1dcd2760b0 | ||
|
|
10f79aae66 | ||
|
|
b58d61c339 | ||
|
|
239765e482 | ||
|
|
bb374c68b1 | ||
|
|
c3e82a6a4e | ||
|
|
f8332bac7f | ||
|
|
7c12e82042 | ||
|
|
e7e66ff873 | ||
|
|
ed1eef7362 | ||
|
|
66d8bb94d6 | ||
|
|
6eb09625ab | ||
|
|
e1bd6a93af | ||
|
|
93800268e4 | ||
|
|
c4d7e5307c | ||
|
|
d6b9a45a39 | ||
|
|
b5e114d834 | ||
|
|
0d1a080a6e | ||
|
|
0444367002 | ||
|
|
7fd3b4d6ba | ||
|
|
4004aafbc5 | ||
|
|
92eae012b3 | ||
|
|
95008eeeaf | ||
|
|
c7a8992043 | ||
|
|
ea2b5f2ccf | ||
|
|
ed9f277421 | ||
|
|
5c14c3b349 | ||
|
|
d8fb31f167 | ||
|
|
fc1bba8817 | ||
|
|
41b86e07f1 | ||
|
|
f5e9bd4f8b | ||
|
|
5e6aaffb09 | ||
|
|
193361c09a | ||
|
|
392447ea33 | ||
|
|
5d4006f291 | ||
|
|
fe6c65f75c | ||
|
|
6dca71cc87 | ||
|
|
adc1f8c983 | ||
|
|
73652e4bba | ||
|
|
02db38ffd3 | ||
|
|
7692195bfa | ||
|
|
c435cbdc7b | ||
|
|
effc281271 | ||
|
|
c2a858b06e | ||
|
|
41eb2e5689 | ||
|
|
67d74a0a27 | ||
|
|
6f5ba54f7b | ||
|
|
f66405e79b | ||
|
|
74d186af16 | ||
|
|
2b228a893a | ||
|
|
187fef0c5a | ||
|
|
4782cab507 | ||
|
|
2eb213741c | ||
|
|
bcbee67709 | ||
|
|
2501cbe55d | ||
|
|
2bd049db87 | ||
|
|
6b8b9ab4fd | ||
|
|
91561da351 | ||
|
|
3ac342782b | ||
|
|
a273c68596 | ||
|
|
fc9db9c381 | ||
|
|
27e8a81c9f | ||
|
|
7959c75df7 | ||
|
|
0bcf5c9c66 | ||
|
|
30db24265e | ||
|
|
316d605899 | ||
|
|
b4257f7767 | ||
|
|
a3f2d5c919 | ||
|
|
cf46469cd5 | ||
|
|
0312fde818 | ||
|
|
bf7917bd67 | ||
|
|
6c0d1b5f15 | ||
|
|
9defa44f0c | ||
|
|
915b53054c | ||
|
|
af2df8d489 | ||
|
|
f81557b4fd | ||
|
|
e273bd1239 | ||
|
|
a63caf4640 | ||
|
|
e7f0234134 | ||
|
|
cccb3e33fb | ||
|
|
340552321e | ||
|
|
98a3fc214d | ||
|
|
77410e5d5e | ||
|
|
3e29c664ac | ||
|
|
6a578cfe3c | ||
|
|
dacc075fe8 | ||
|
|
55418e2bc0 | ||
|
|
f2b5c0f3a8 | ||
|
|
5ebdf5dba9 | ||
|
|
8e045764df | ||
|
|
503d3d77e6 | ||
|
|
81bd7d97c5 | ||
|
|
8f57925067 | ||
|
|
9bf99db82e | ||
|
|
5615cdbf1a | ||
|
|
d802ae0058 | ||
|
|
8f2278198c | ||
|
|
10937a5a4e | ||
|
|
6aff6e9804 | ||
|
|
95477cae7e |
44
.github/workflows/build.yml
vendored
44
.github/workflows/build.yml
vendored
@@ -79,10 +79,10 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Haskell
|
||||
uses: haskell-actions/setup@v2
|
||||
uses: haskell/actions/setup@v2
|
||||
with:
|
||||
ghc-version: "9.6.2"
|
||||
cabal-version: "3.10.1.0"
|
||||
ghc-version: "8.10.7"
|
||||
cabal-version: "latest"
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v3
|
||||
@@ -188,7 +188,7 @@ jobs:
|
||||
APPLE_SIMPLEX_NOTARIZATION_APPLE_ID: ${{ secrets.APPLE_SIMPLEX_NOTARIZATION_APPLE_ID }}
|
||||
APPLE_SIMPLEX_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_SIMPLEX_NOTARIZATION_PASSWORD }}
|
||||
run: |
|
||||
scripts/ci/build-desktop-mac.sh
|
||||
scripts/build-desktop-mac.sh
|
||||
path=$(echo $PWD/apps/multiplatform/release/main/dmg/SimpleX-*.dmg)
|
||||
echo "package_path=$path" >> $GITHUB_OUTPUT
|
||||
echo "package_hash=$(echo SHA2-512\(${{ matrix.desktop_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT
|
||||
@@ -259,13 +259,40 @@ jobs:
|
||||
# Unix /
|
||||
|
||||
# / Windows
|
||||
# rm -rf dist-newstyle/src/direct-sq* is here because of the bug in cabal's dependency which prevents second build from finishing
|
||||
|
||||
# * In powershell multiline commands do not fail if individual commands fail - https://github.community/t/multiline-commands-on-windows-do-not-fail-if-individual-commands-fail/16753
|
||||
# * And GitHub Actions does not support parameterizing shell in a matrix job - https://github.community/t/using-matrix-to-specify-shell-is-it-possible/17065
|
||||
|
||||
- name: 'Setup MSYS2'
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest'
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: ucrt64
|
||||
update: true
|
||||
install: >-
|
||||
git
|
||||
perl
|
||||
make
|
||||
pacboy: >-
|
||||
toolchain:p
|
||||
cmake:p
|
||||
|
||||
|
||||
- name: Windows build
|
||||
id: windows_build
|
||||
if: matrix.os == 'windows-latest'
|
||||
shell: bash
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
export PATH=$PATH:/c/ghcup/bin
|
||||
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
|
||||
echo "ignore-project: False" >> cabal.project.local
|
||||
echo "package direct-sqlcipher" >> cabal.project.local
|
||||
echo " flags: +openssl" >> cabal.project.local
|
||||
echo " extra-include-dirs: $openssl_windows_style_path\include" >> cabal.project.local
|
||||
echo " extra-lib-dirs: $openssl_windows_style_path" >> cabal.project.local
|
||||
|
||||
rm -rf dist-newstyle/src/direct-sq*
|
||||
sed -i "s/, unix /--, unix /" simplex-chat.cabal
|
||||
cabal build --enable-tests
|
||||
@@ -296,10 +323,9 @@ jobs:
|
||||
- name: Windows build desktop
|
||||
id: windows_desktop_build
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest'
|
||||
env:
|
||||
SIMPLEX_CI_REPO_URL: ${{ secrets.SIMPLEX_CI_REPO_URL }}
|
||||
shell: bash
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
export PATH=$PATH:/c/ghcup/bin
|
||||
scripts/desktop/build-lib-windows.sh
|
||||
cd apps/multiplatform
|
||||
./gradlew packageMsi
|
||||
|
||||
@@ -8,11 +8,11 @@ RUN a=$(arch); curl https://downloads.haskell.org/~ghcup/$a-linux-ghcup -o /usr/
|
||||
chmod +x /usr/bin/ghcup
|
||||
|
||||
# Install ghc
|
||||
RUN ghcup install ghc 9.6.2
|
||||
RUN ghcup install ghc 9.6.3
|
||||
# Install cabal
|
||||
RUN ghcup install cabal 3.10.1.0
|
||||
# Set both as default
|
||||
RUN ghcup set ghc 9.6.2 && \
|
||||
RUN ghcup set ghc 9.6.3 && \
|
||||
ghcup set cabal 3.10.1.0
|
||||
|
||||
COPY . /project
|
||||
|
||||
@@ -55,7 +55,6 @@ class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
didReceiveRemoteNotification userInfo: [AnyHashable : Any],
|
||||
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
|
||||
logger.debug("AppDelegate: didReceiveRemoteNotification")
|
||||
print("*** userInfo", userInfo)
|
||||
let m = ChatModel.shared
|
||||
if let ntfData = userInfo["notificationData"] as? [AnyHashable : Any],
|
||||
m.notificationMode != .off {
|
||||
@@ -121,6 +120,10 @@ class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
|
||||
BGManager.shared.receiveMessages(complete)
|
||||
}
|
||||
|
||||
static func keepScreenOn(_ on: Bool) {
|
||||
UIApplication.shared.isIdleTimerDisabled = on
|
||||
}
|
||||
}
|
||||
|
||||
class SceneDelegate: NSObject, ObservableObject, UIWindowSceneDelegate {
|
||||
|
||||
@@ -46,6 +46,7 @@ class AudioRecorder {
|
||||
audioRecorder?.record(forDuration: MAX_VOICE_MESSAGE_LENGTH)
|
||||
|
||||
await MainActor.run {
|
||||
AppDelegate.keepScreenOn(true)
|
||||
recordingTimer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { timer in
|
||||
guard let time = self.audioRecorder?.currentTime else { return }
|
||||
self.onTimer?(time)
|
||||
@@ -57,6 +58,10 @@ class AudioRecorder {
|
||||
}
|
||||
return nil
|
||||
} catch let error {
|
||||
await MainActor.run {
|
||||
AppDelegate.keepScreenOn(false)
|
||||
}
|
||||
try? av.setCategory(AVAudioSession.Category.soloAmbient)
|
||||
logger.error("AudioRecorder startAudioRecording error \(error.localizedDescription)")
|
||||
return .error(error.localizedDescription)
|
||||
}
|
||||
@@ -71,6 +76,8 @@ class AudioRecorder {
|
||||
timer.invalidate()
|
||||
}
|
||||
recordingTimer = nil
|
||||
AppDelegate.keepScreenOn(false)
|
||||
try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.soloAmbient)
|
||||
}
|
||||
|
||||
private func checkPermission() async -> Bool {
|
||||
@@ -121,14 +128,19 @@ class AudioPlayer: NSObject, AVAudioPlayerDelegate {
|
||||
|
||||
playbackTimer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { timer in
|
||||
if self.audioPlayer?.isPlaying ?? false {
|
||||
AppDelegate.keepScreenOn(true)
|
||||
guard let time = self.audioPlayer?.currentTime else { return }
|
||||
self.onTimer?(time)
|
||||
AudioPlayer.changeAudioSession(true)
|
||||
} else {
|
||||
AudioPlayer.changeAudioSession(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func pause() {
|
||||
audioPlayer?.pause()
|
||||
AppDelegate.keepScreenOn(false)
|
||||
}
|
||||
|
||||
func play() {
|
||||
@@ -149,6 +161,8 @@ class AudioPlayer: NSObject, AVAudioPlayerDelegate {
|
||||
func stop() {
|
||||
if let player = audioPlayer {
|
||||
player.stop()
|
||||
AppDelegate.keepScreenOn(false)
|
||||
AudioPlayer.changeAudioSession(false)
|
||||
}
|
||||
audioPlayer = nil
|
||||
if let timer = playbackTimer {
|
||||
@@ -157,6 +171,24 @@ class AudioPlayer: NSObject, AVAudioPlayerDelegate {
|
||||
playbackTimer = nil
|
||||
}
|
||||
|
||||
static func changeAudioSession(_ playback: Bool) {
|
||||
// When there is a audio recording, setting any other category will disable sound
|
||||
if AVAudioSession.sharedInstance().category == .playAndRecord {
|
||||
return
|
||||
}
|
||||
if playback {
|
||||
if AVAudioSession.sharedInstance().category != .playback {
|
||||
logger.log("AudioSession: playback")
|
||||
try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, options: .duckOthers)
|
||||
}
|
||||
} else {
|
||||
if AVAudioSession.sharedInstance().category != .soloAmbient {
|
||||
logger.log("AudioSession: soloAmbient")
|
||||
try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.soloAmbient)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
|
||||
stop()
|
||||
self.onFinishPlayback?()
|
||||
|
||||
@@ -64,7 +64,7 @@ final class ChatModel: ObservableObject {
|
||||
@Published var reversedChatItems: [ChatItem] = []
|
||||
var chatItemStatuses: Dictionary<Int64, CIStatus> = [:]
|
||||
@Published var chatToTop: String?
|
||||
@Published var groupMembers: [GroupMember] = []
|
||||
@Published var groupMembers: [GMember] = []
|
||||
// items in the terminal view
|
||||
@Published var showingTerminal = false
|
||||
@Published var terminalItems: [TerminalItem] = []
|
||||
@@ -85,6 +85,8 @@ final class ChatModel: ObservableObject {
|
||||
@Published var activeCall: Call?
|
||||
@Published var callCommand: WCallCommand?
|
||||
@Published var showCallView = false
|
||||
// remote desktop
|
||||
@Published var remoteCtrlSession: RemoteCtrlSession?
|
||||
// currently showing QR code
|
||||
@Published var connReqInv: String?
|
||||
// audio recording and playback
|
||||
@@ -110,6 +112,10 @@ final class ChatModel: ObservableObject {
|
||||
notificationMode == .periodic || ntfEnablePeriodicGroupDefault.get()
|
||||
}
|
||||
|
||||
var activeRemoteCtrl: Bool {
|
||||
remoteCtrlSession?.active ?? false
|
||||
}
|
||||
|
||||
func getUser(_ userId: Int64) -> User? {
|
||||
currentUser?.userId == userId
|
||||
? currentUser
|
||||
@@ -163,6 +169,10 @@ final class ChatModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
func getGroupMember(_ groupMemberId: Int64) -> GMember? {
|
||||
groupMembers.first { $0.groupMemberId == groupMemberId }
|
||||
}
|
||||
|
||||
private func getChatIndex(_ id: String) -> Int? {
|
||||
chats.firstIndex(where: { $0.id == id })
|
||||
}
|
||||
@@ -176,6 +186,7 @@ final class ChatModel: ObservableObject {
|
||||
func updateChatInfo(_ cInfo: ChatInfo) {
|
||||
if let i = getChatIndex(cInfo.id) {
|
||||
chats[i].chatInfo = cInfo
|
||||
chats[i].created = Date.now
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +200,7 @@ final class ChatModel: ObservableObject {
|
||||
|
||||
func updateContactConnectionStats(_ contact: Contact, _ connectionStats: ConnectionStats) {
|
||||
var updatedConn = contact.activeConn
|
||||
updatedConn.connectionStats = connectionStats
|
||||
updatedConn?.connectionStats = connectionStats
|
||||
var updatedContact = contact
|
||||
updatedContact.activeConn = updatedConn
|
||||
updateContact(updatedContact)
|
||||
@@ -339,7 +350,7 @@ final class ChatModel: ObservableObject {
|
||||
reversedChatItems[i].viewTimestamp = .now
|
||||
}
|
||||
|
||||
private func getChatItemIndex(_ cItem: ChatItem) -> Int? {
|
||||
func getChatItemIndex(_ cItem: ChatItem) -> Int? {
|
||||
reversedChatItems.firstIndex(where: { $0.id == cItem.id })
|
||||
}
|
||||
|
||||
@@ -528,27 +539,62 @@ final class ChatModel: ObservableObject {
|
||||
users.filter { !$0.user.activeUser }.reduce(0, { unread, next -> Int in unread + next.unreadCount })
|
||||
}
|
||||
|
||||
func getConnectedMemberNames(_ ci: ChatItem) -> [String] {
|
||||
guard var i = getChatItemIndex(ci) else { return [] }
|
||||
// this function analyses "connected" events and assumes that each member will be there only once
|
||||
func getConnectedMemberNames(_ chatItem: ChatItem) -> (Int, [String]) {
|
||||
var count = 0
|
||||
var ns: [String] = []
|
||||
while i < reversedChatItems.count, let m = reversedChatItems[i].memberConnected {
|
||||
ns.append(m.displayName)
|
||||
i += 1
|
||||
if let ciCategory = chatItem.mergeCategory,
|
||||
var i = getChatItemIndex(chatItem) {
|
||||
while i < reversedChatItems.count {
|
||||
let ci = reversedChatItems[i]
|
||||
if ci.mergeCategory != ciCategory { break }
|
||||
if let m = ci.memberConnected {
|
||||
ns.append(m.displayName)
|
||||
}
|
||||
count += 1
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
return ns
|
||||
return (count, ns)
|
||||
}
|
||||
|
||||
func getChatItemNeighbors(_ ci: ChatItem) -> (ChatItem?, ChatItem?) {
|
||||
if let i = getChatItemIndex(ci) {
|
||||
return (
|
||||
i + 1 < reversedChatItems.count ? reversedChatItems[i + 1] : nil,
|
||||
i - 1 >= 0 ? reversedChatItems[i - 1] : nil
|
||||
)
|
||||
// returns the index of the passed item and the next item (it has smaller index)
|
||||
func getNextChatItem(_ ci: ChatItem) -> (Int?, ChatItem?) {
|
||||
if let i = getChatItemIndex(ci) {
|
||||
(i, i > 0 ? reversedChatItems[i - 1] : nil)
|
||||
} else {
|
||||
return (nil, nil)
|
||||
(nil, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// returns the index of the first item in the same merged group (the first hidden item)
|
||||
// and the previous visible item with another merge category
|
||||
func getPrevShownChatItem(_ ciIndex: Int?, _ ciCategory: CIMergeCategory?) -> (Int?, ChatItem?) {
|
||||
guard var i = ciIndex else { return (nil, nil) }
|
||||
let fst = reversedChatItems.count - 1
|
||||
while i < fst {
|
||||
i = i + 1
|
||||
let ci = reversedChatItems[i]
|
||||
if ciCategory == nil || ciCategory != ci.mergeCategory {
|
||||
return (i - 1, ci)
|
||||
}
|
||||
}
|
||||
return (i, nil)
|
||||
}
|
||||
|
||||
// returns the previous member in the same merge group and the count of members in this group
|
||||
func getPrevHiddenMember(_ member: GroupMember, _ range: ClosedRange<Int>) -> (GroupMember?, Int) {
|
||||
var prevMember: GroupMember? = nil
|
||||
var memberIds: Set<Int64> = []
|
||||
for i in range {
|
||||
if case let .groupRcv(m) = reversedChatItems[i].chatDir {
|
||||
if prevMember == nil && m.groupMemberId != member.groupMemberId { prevMember = m }
|
||||
memberIds.insert(m.groupMemberId)
|
||||
}
|
||||
}
|
||||
return (prevMember, memberIds.count)
|
||||
}
|
||||
|
||||
func popChat(_ id: String) {
|
||||
if let i = getChatIndex(id) {
|
||||
popChat_(i)
|
||||
@@ -583,13 +629,14 @@ final class ChatModel: ObservableObject {
|
||||
}
|
||||
// update current chat
|
||||
if chatId == groupInfo.id {
|
||||
if let i = groupMembers.firstIndex(where: { $0.id == member.id }) {
|
||||
if let i = groupMembers.firstIndex(where: { $0.groupMemberId == member.groupMemberId }) {
|
||||
withAnimation(.default) {
|
||||
self.groupMembers[i] = member
|
||||
self.groupMembers[i].wrapped = member
|
||||
self.groupMembers[i].created = Date.now
|
||||
}
|
||||
return false
|
||||
} else {
|
||||
withAnimation { groupMembers.append(member) }
|
||||
withAnimation { groupMembers.append(GMember(member)) }
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
@@ -598,11 +645,10 @@ final class ChatModel: ObservableObject {
|
||||
}
|
||||
|
||||
func updateGroupMemberConnectionStats(_ groupInfo: GroupInfo, _ member: GroupMember, _ connectionStats: ConnectionStats) {
|
||||
if let conn = member.activeConn {
|
||||
var updatedConn = conn
|
||||
updatedConn.connectionStats = connectionStats
|
||||
if var conn = member.activeConn {
|
||||
conn.connectionStats = connectionStats
|
||||
var updatedMember = member
|
||||
updatedMember.activeConn = updatedConn
|
||||
updatedMember.activeConn = conn
|
||||
_ = upsertGroupMember(groupInfo, updatedMember)
|
||||
}
|
||||
}
|
||||
@@ -631,11 +677,17 @@ final class ChatModel: ObservableObject {
|
||||
}
|
||||
|
||||
func setContactNetworkStatus(_ contact: Contact, _ status: NetworkStatus) {
|
||||
networkStatuses[contact.activeConn.agentConnId] = status
|
||||
if let conn = contact.activeConn {
|
||||
networkStatuses[conn.agentConnId] = status
|
||||
}
|
||||
}
|
||||
|
||||
func contactNetworkStatus(_ contact: Contact) -> NetworkStatus {
|
||||
networkStatuses[contact.activeConn.agentConnId] ?? .unknown
|
||||
if let conn = contact.activeConn {
|
||||
networkStatuses[conn.agentConnId] ?? .unknown
|
||||
} else {
|
||||
.unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -700,3 +752,54 @@ final class Chat: ObservableObject, Identifiable {
|
||||
|
||||
public static var sampleData: Chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: [])
|
||||
}
|
||||
|
||||
final class GMember: ObservableObject, Identifiable {
|
||||
@Published var wrapped: GroupMember
|
||||
var created = Date.now
|
||||
|
||||
init(_ member: GroupMember) {
|
||||
self.wrapped = member
|
||||
}
|
||||
|
||||
var id: String { wrapped.id }
|
||||
var groupId: Int64 { wrapped.groupId }
|
||||
var groupMemberId: Int64 { wrapped.groupMemberId }
|
||||
var displayName: String { wrapped.displayName }
|
||||
var viewId: String { get { "\(wrapped.id) \(created.timeIntervalSince1970)" } }
|
||||
static let sampleData = GMember(GroupMember.sampleData)
|
||||
}
|
||||
|
||||
struct RemoteCtrlSession {
|
||||
var ctrlAppInfo: CtrlAppInfo?
|
||||
var appVersion: String
|
||||
var sessionState: UIRemoteCtrlSessionState
|
||||
|
||||
func updateState(_ state: UIRemoteCtrlSessionState) -> RemoteCtrlSession {
|
||||
RemoteCtrlSession(ctrlAppInfo: ctrlAppInfo, appVersion: appVersion, sessionState: state)
|
||||
}
|
||||
|
||||
var active: Bool {
|
||||
if case .connected = sessionState { true } else { false }
|
||||
}
|
||||
|
||||
var discovery: Bool {
|
||||
if case .searching = sessionState { true } else { false }
|
||||
}
|
||||
|
||||
var sessionCode: String? {
|
||||
switch sessionState {
|
||||
case let .pendingConfirmation(_, sessionCode): sessionCode
|
||||
case let .connected(_, sessionCode): sessionCode
|
||||
default: nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum UIRemoteCtrlSessionState {
|
||||
case starting
|
||||
case searching
|
||||
case found(remoteCtrl: RemoteCtrlInfo, compatible: Bool)
|
||||
case connecting(remoteCtrl_: RemoteCtrlInfo?)
|
||||
case pendingConfirmation(remoteCtrl_: RemoteCtrlInfo?, sessionCode: String)
|
||||
case connected(remoteCtrl: RemoteCtrlInfo, sessionCode: String)
|
||||
}
|
||||
|
||||
@@ -502,6 +502,10 @@ func apiSetChatSettings(type: ChatType, id: Int64, chatSettings: ChatSettings) a
|
||||
try await sendCommandOkResp(.apiSetChatSettings(type: type, id: id, chatSettings: chatSettings))
|
||||
}
|
||||
|
||||
func apiSetMemberSettings(_ groupId: Int64, _ groupMemberId: Int64, _ memberSettings: GroupMemberSettings) async throws {
|
||||
try await sendCommandOkResp(.apiSetMemberSettings(groupId: groupId, groupMemberId: groupMemberId, memberSettings: memberSettings))
|
||||
}
|
||||
|
||||
func apiContactInfo(_ contactId: Int64) async throws -> (ConnectionStats?, Profile?) {
|
||||
let r = await chatSendCmd(.apiContactInfo(contactId: contactId))
|
||||
if case let .contactInfo(_, _, connStats, customUserProfile) = r { return (connStats, customUserProfile) }
|
||||
@@ -671,6 +675,18 @@ private func connectionErrorAlert(_ r: ChatResponse) -> Alert {
|
||||
}
|
||||
}
|
||||
|
||||
func apiConnectContactViaAddress(incognito: Bool, contactId: Int64) async -> (Contact?, Alert?) {
|
||||
guard let userId = ChatModel.shared.currentUser?.userId else {
|
||||
logger.error("apiConnectContactViaAddress: no current user")
|
||||
return (nil, nil)
|
||||
}
|
||||
let r = await chatSendCmd(.apiConnectContactViaAddress(userId: userId, incognito: incognito, contactId: contactId))
|
||||
if case let .sentInvitationToContact(_, contact, _) = r { return (contact, nil) }
|
||||
logger.error("apiConnectContactViaAddress error: \(responseError(r))")
|
||||
let alert = connectionErrorAlert(r)
|
||||
return (nil, alert)
|
||||
}
|
||||
|
||||
func apiDeleteChat(type: ChatType, id: Int64, notify: Bool? = nil) async throws {
|
||||
let r = await chatSendCmd(.apiDeleteChat(type: type, id: id, notify: notify), bgTask: false)
|
||||
if case .direct = type, case .contactDeleted = r { return }
|
||||
@@ -720,8 +736,9 @@ func apiUpdateProfile(profile: Profile) async throws -> (Profile, [Contact])? {
|
||||
let userId = try currentUserId("apiUpdateProfile")
|
||||
let r = await chatSendCmd(.apiUpdateProfile(userId: userId, profile: profile))
|
||||
switch r {
|
||||
case .userProfileNoChange: return nil
|
||||
case .userProfileNoChange: return (profile, [])
|
||||
case let .userProfileUpdated(_, _, toProfile, updateSummary): return (toProfile, updateSummary.changedContacts)
|
||||
case .chatCmdError(_, .errorStore(.duplicateName)): return nil;
|
||||
default: throw r
|
||||
}
|
||||
}
|
||||
@@ -888,6 +905,46 @@ func apiCancelFile(fileId: Int64) async -> AChatItem? {
|
||||
}
|
||||
}
|
||||
|
||||
func setLocalDeviceName(_ displayName: String) throws {
|
||||
try sendCommandOkRespSync(.setLocalDeviceName(displayName: displayName))
|
||||
}
|
||||
|
||||
func connectRemoteCtrl(desktopAddress: String) async throws -> (RemoteCtrlInfo?, CtrlAppInfo, String) {
|
||||
let r = await chatSendCmd(.connectRemoteCtrl(xrcpInvitation: desktopAddress))
|
||||
if case let .remoteCtrlConnecting(rc_, ctrlAppInfo, v) = r { return (rc_, ctrlAppInfo, v) }
|
||||
throw r
|
||||
}
|
||||
|
||||
func findKnownRemoteCtrl() async throws {
|
||||
try await sendCommandOkResp(.findKnownRemoteCtrl)
|
||||
}
|
||||
|
||||
func confirmRemoteCtrl(_ rcId: Int64) async throws -> (RemoteCtrlInfo?, CtrlAppInfo, String) {
|
||||
let r = await chatSendCmd(.confirmRemoteCtrl(remoteCtrlId: rcId))
|
||||
if case let .remoteCtrlConnecting(rc_, ctrlAppInfo, v) = r { return (rc_, ctrlAppInfo, v) }
|
||||
throw r
|
||||
}
|
||||
|
||||
func verifyRemoteCtrlSession(_ sessCode: String) async throws -> RemoteCtrlInfo {
|
||||
let r = await chatSendCmd(.verifyRemoteCtrlSession(sessionCode: sessCode))
|
||||
if case let .remoteCtrlConnected(rc) = r { return rc }
|
||||
throw r
|
||||
}
|
||||
|
||||
func listRemoteCtrls() throws -> [RemoteCtrlInfo] {
|
||||
let r = chatSendCmdSync(.listRemoteCtrls)
|
||||
if case let .remoteCtrlList(rcInfo) = r { return rcInfo }
|
||||
throw r
|
||||
}
|
||||
|
||||
func stopRemoteCtrl() async throws {
|
||||
try await sendCommandOkResp(.stopRemoteCtrl)
|
||||
}
|
||||
|
||||
func deleteRemoteCtrl(_ rcId: Int64) async throws {
|
||||
try await sendCommandOkResp(.deleteRemoteCtrl(remoteCtrlId: rcId))
|
||||
}
|
||||
|
||||
func networkErrorAlert(_ r: ChatResponse) -> Alert? {
|
||||
switch r {
|
||||
case let .chatCmdError(_, .errorAgent(.BROKER(addr, .TIMEOUT))):
|
||||
@@ -1016,9 +1073,15 @@ private func sendCommandOkResp(_ cmd: ChatCommand) async throws {
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiNewGroup(_ p: GroupProfile) throws -> GroupInfo {
|
||||
private func sendCommandOkRespSync(_ cmd: ChatCommand) throws {
|
||||
let r = chatSendCmdSync(cmd)
|
||||
if case .cmdOk = r { return }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiNewGroup(incognito: Bool, groupProfile: GroupProfile) throws -> GroupInfo {
|
||||
let userId = try currentUserId("apiNewGroup")
|
||||
let r = chatSendCmdSync(.apiNewGroup(userId: userId, groupProfile: p))
|
||||
let r = chatSendCmdSync(.apiNewGroup(userId: userId, incognito: incognito, groupProfile: groupProfile))
|
||||
if case let .groupCreated(_, groupInfo) = r { return groupInfo }
|
||||
throw r
|
||||
}
|
||||
@@ -1078,8 +1141,8 @@ func apiListMembers(_ groupId: Int64) async -> [GroupMember] {
|
||||
return []
|
||||
}
|
||||
|
||||
func filterMembersToAdd(_ ms: [GroupMember]) -> [Contact] {
|
||||
let memberContactIds = ms.compactMap{ m in m.memberCurrent ? m.memberContactId : nil }
|
||||
func filterMembersToAdd(_ ms: [GMember]) -> [Contact] {
|
||||
let memberContactIds = ms.compactMap{ m in m.wrapped.memberCurrent ? m.wrapped.memberContactId : nil }
|
||||
return ChatModel.shared.chats
|
||||
.compactMap{ $0.chatInfo.contact }
|
||||
.filter{ !memberContactIds.contains($0.apiId) }
|
||||
@@ -1321,8 +1384,10 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
if active(user) && contact.directOrUsed {
|
||||
await MainActor.run {
|
||||
m.updateContact(contact)
|
||||
m.dismissConnReqView(contact.activeConn.id)
|
||||
m.removeChat(contact.activeConn.id)
|
||||
if let conn = contact.activeConn {
|
||||
m.dismissConnReqView(conn.id)
|
||||
m.removeChat(conn.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
if contact.directOrUsed {
|
||||
@@ -1335,8 +1400,10 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
if active(user) && contact.directOrUsed {
|
||||
await MainActor.run {
|
||||
m.updateContact(contact)
|
||||
m.dismissConnReqView(contact.activeConn.id)
|
||||
m.removeChat(contact.activeConn.id)
|
||||
if let conn = contact.activeConn {
|
||||
m.dismissConnReqView(conn.id)
|
||||
m.removeChat(conn.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
case let .receivedContactRequest(user, contactRequest):
|
||||
@@ -1361,6 +1428,12 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
m.updateChatInfo(cInfo)
|
||||
}
|
||||
}
|
||||
case let .groupMemberUpdated(user, groupInfo, _, toMember):
|
||||
if active(user) {
|
||||
await MainActor.run {
|
||||
_ = m.upsertGroupMember(groupInfo, toMember)
|
||||
}
|
||||
}
|
||||
case let .contactsMerged(user, intoContact, mergedContact):
|
||||
if active(user) && m.hasChat(mergedContact.id) {
|
||||
await MainActor.run {
|
||||
@@ -1469,9 +1542,19 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
|
||||
await MainActor.run {
|
||||
m.updateGroup(groupInfo)
|
||||
if let hostContact = hostContact {
|
||||
m.dismissConnReqView(hostContact.activeConn.id)
|
||||
m.removeChat(hostContact.activeConn.id)
|
||||
if let conn = hostContact?.activeConn {
|
||||
m.dismissConnReqView(conn.id)
|
||||
m.removeChat(conn.id)
|
||||
}
|
||||
}
|
||||
case let .groupLinkConnecting(user, groupInfo, hostMember):
|
||||
if !active(user) { return }
|
||||
|
||||
await MainActor.run {
|
||||
m.updateGroup(groupInfo)
|
||||
if let hostConn = hostMember.activeConn {
|
||||
m.dismissConnReqView(hostConn.id)
|
||||
m.removeChat(hostConn.id)
|
||||
}
|
||||
}
|
||||
case let .joinedGroupMemberConnecting(user, groupInfo, _, member):
|
||||
@@ -1533,10 +1616,11 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
m.updateGroup(toGroup)
|
||||
}
|
||||
}
|
||||
case let .memberRole(user, groupInfo, _, _, _, _):
|
||||
case let .memberRole(user, groupInfo, byMember: _, member: member, fromRole: _, toRole: _):
|
||||
if active(user) {
|
||||
await MainActor.run {
|
||||
m.updateGroup(groupInfo)
|
||||
_ = m.upsertGroupMember(groupInfo, member)
|
||||
}
|
||||
}
|
||||
case let .newMemberContactReceivedInv(user, contact, _, _):
|
||||
@@ -1632,6 +1716,39 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
await MainActor.run {
|
||||
m.updateGroupMemberConnectionStats(groupInfo, member, ratchetSyncProgress.connectionStats)
|
||||
}
|
||||
case let .remoteCtrlFound(remoteCtrl, ctrlAppInfo_, appVersion, compatible):
|
||||
await MainActor.run {
|
||||
if let sess = m.remoteCtrlSession, case .searching = sess.sessionState {
|
||||
let state = UIRemoteCtrlSessionState.found(remoteCtrl: remoteCtrl, compatible: compatible)
|
||||
m.remoteCtrlSession = RemoteCtrlSession(
|
||||
ctrlAppInfo: ctrlAppInfo_,
|
||||
appVersion: appVersion,
|
||||
sessionState: state
|
||||
)
|
||||
}
|
||||
}
|
||||
case let .remoteCtrlSessionCode(remoteCtrl_, sessionCode):
|
||||
await MainActor.run {
|
||||
let state = UIRemoteCtrlSessionState.pendingConfirmation(remoteCtrl_: remoteCtrl_, sessionCode: sessionCode)
|
||||
m.remoteCtrlSession = m.remoteCtrlSession?.updateState(state)
|
||||
}
|
||||
case let .remoteCtrlConnected(remoteCtrl):
|
||||
// TODO currently it is returned in response to command, so it is redundant
|
||||
await MainActor.run {
|
||||
let state = UIRemoteCtrlSessionState.connected(remoteCtrl: remoteCtrl, sessionCode: m.remoteCtrlSession?.sessionCode ?? "")
|
||||
m.remoteCtrlSession = m.remoteCtrlSession?.updateState(state)
|
||||
}
|
||||
case .remoteCtrlStopped:
|
||||
// This delay is needed to cancel the session that fails on network failure,
|
||||
// e.g. when user did not grant permission to access local network yet.
|
||||
if let sess = m.remoteCtrlSession {
|
||||
m.remoteCtrlSession = nil
|
||||
if case .connected = sess.sessionState {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
switchToLocalSession()
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
logger.debug("unsupported event: \(res.responseType)")
|
||||
}
|
||||
@@ -1645,6 +1762,19 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
}
|
||||
}
|
||||
|
||||
func switchToLocalSession() {
|
||||
let m = ChatModel.shared
|
||||
m.remoteCtrlSession = nil
|
||||
do {
|
||||
m.users = try listUsers()
|
||||
try getUserChatData()
|
||||
let statuses = (try apiGetNetworkStatuses()).map { s in (s.agentConnId, s.networkStatus) }
|
||||
m.networkStatuses = Dictionary(uniqueKeysWithValues: statuses)
|
||||
} catch let error {
|
||||
logger.debug("error updating chat data: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
|
||||
func active(_ user: any UserLike) -> Bool {
|
||||
user.userId == ChatModel.shared.currentUser?.id
|
||||
}
|
||||
|
||||
@@ -26,7 +26,10 @@ struct SimpleXApp: App {
|
||||
@State private var showInitializationView = false
|
||||
|
||||
init() {
|
||||
hs_init(0, nil)
|
||||
DispatchQueue.global(qos: .background).sync {
|
||||
haskell_init()
|
||||
// hs_init(0, nil)
|
||||
}
|
||||
UserDefaults.standard.register(defaults: appDefaults)
|
||||
setGroupDefaults()
|
||||
registerGroupDefaults()
|
||||
|
||||
@@ -39,6 +39,7 @@ struct ActiveCallView: View {
|
||||
}
|
||||
.onAppear {
|
||||
logger.debug("ActiveCallView: appear client is nil \(client == nil), scenePhase \(String(describing: scenePhase), privacy: .public), canConnectCall \(canConnectCall)")
|
||||
AppDelegate.keepScreenOn(true)
|
||||
createWebRTCClient()
|
||||
dismissAllSheets()
|
||||
}
|
||||
@@ -48,6 +49,7 @@ struct ActiveCallView: View {
|
||||
}
|
||||
.onDisappear {
|
||||
logger.debug("ActiveCallView: disappear")
|
||||
AppDelegate.keepScreenOn(false)
|
||||
client?.endCall()
|
||||
}
|
||||
.onChange(of: m.callCommand) { _ in sendCommandToClient()}
|
||||
|
||||
@@ -108,7 +108,6 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
|
||||
try audioSession.setActive(true)
|
||||
logger.debug("audioSession activated")
|
||||
} catch {
|
||||
print(error)
|
||||
logger.error("failed activating audio session")
|
||||
}
|
||||
}
|
||||
@@ -121,7 +120,6 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
|
||||
try audioSession.setActive(false)
|
||||
logger.debug("audioSession deactivated")
|
||||
} catch {
|
||||
print(error)
|
||||
logger.error("failed deactivating audio session")
|
||||
}
|
||||
suspendOnEndCall()
|
||||
|
||||
@@ -242,7 +242,7 @@ struct ChatInfoView: View {
|
||||
}
|
||||
.actionSheet(isPresented: $showDeleteContactActionSheet) {
|
||||
if contact.ready && contact.active {
|
||||
ActionSheet(
|
||||
return ActionSheet(
|
||||
title: Text("Delete contact?\nThis cannot be undone!"),
|
||||
buttons: [
|
||||
.destructive(Text("Delete and notify contact")) { deleteContact(notify: true) },
|
||||
@@ -251,7 +251,7 @@ struct ChatInfoView: View {
|
||||
]
|
||||
)
|
||||
} else {
|
||||
ActionSheet(
|
||||
return ActionSheet(
|
||||
title: Text("Delete contact?\nThis cannot be undone!"),
|
||||
buttons: [
|
||||
.destructive(Text("Delete")) { deleteContact() },
|
||||
@@ -338,7 +338,7 @@ struct ChatInfoView: View {
|
||||
verify: { code in
|
||||
if let r = apiVerifyContact(chat.chatInfo.apiId, connectionCode: code) {
|
||||
let (verified, existingCode) = r
|
||||
contact.activeConn.connectionCode = verified ? SecurityCode(securityCode: existingCode, verifiedAt: .now) : nil
|
||||
contact.activeConn?.connectionCode = verified ? SecurityCode(securityCode: existingCode, verifiedAt: .now) : nil
|
||||
connectionCode = existingCode
|
||||
DispatchQueue.main.async {
|
||||
chat.chatInfo = .direct(contact: contact)
|
||||
|
||||
@@ -11,7 +11,7 @@ import SimpleXChat
|
||||
|
||||
struct CICallItemView: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
var chatInfo: ChatInfo
|
||||
@ObservedObject var chat: Chat
|
||||
var chatItem: ChatItem
|
||||
var status: CICallStatus
|
||||
var duration: Int
|
||||
@@ -60,7 +60,7 @@ struct CICallItemView: View {
|
||||
|
||||
|
||||
@ViewBuilder private func acceptCallButton() -> some View {
|
||||
if case let .direct(contact) = chatInfo {
|
||||
if case let .direct(contact) = chat.chatInfo {
|
||||
Button {
|
||||
if let invitation = m.callInvitations[contact.id] {
|
||||
CallController.shared.answerCall(invitation: invitation)
|
||||
|
||||
@@ -10,20 +10,92 @@ import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct CIChatFeatureView: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
var chatItem: ChatItem
|
||||
@Binding var revealed: Bool
|
||||
var feature: Feature
|
||||
var icon: String? = nil
|
||||
var iconColor: Color
|
||||
|
||||
var body: some View {
|
||||
if !revealed, let fs = mergedFeautures() {
|
||||
HStack {
|
||||
ForEach(fs, content: featureIconView)
|
||||
}
|
||||
.padding(.horizontal, 6)
|
||||
.padding(.vertical, 6)
|
||||
} else {
|
||||
fullFeatureView
|
||||
}
|
||||
}
|
||||
|
||||
private struct FeatureInfo: Identifiable {
|
||||
var icon: String
|
||||
var scale: CGFloat
|
||||
var color: Color
|
||||
var param: String?
|
||||
|
||||
init(_ f: Feature, _ color: Color, _ param: Int?) {
|
||||
self.icon = f.iconFilled
|
||||
self.scale = f.iconScale
|
||||
self.color = color
|
||||
self.param = f.hasParam && param != nil ? timeText(param) : nil
|
||||
}
|
||||
|
||||
var id: String {
|
||||
"\(icon) \(color) \(param ?? "")"
|
||||
}
|
||||
}
|
||||
|
||||
private func mergedFeautures() -> [FeatureInfo]? {
|
||||
var fs: [FeatureInfo] = []
|
||||
var icons: Set<String> = []
|
||||
if var i = m.getChatItemIndex(chatItem) {
|
||||
while i < m.reversedChatItems.count,
|
||||
let f = featureInfo(m.reversedChatItems[i]) {
|
||||
if !icons.contains(f.icon) {
|
||||
fs.insert(f, at: 0)
|
||||
icons.insert(f.icon)
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
return fs.count > 1 ? fs : nil
|
||||
}
|
||||
|
||||
private func featureInfo(_ ci: ChatItem) -> FeatureInfo? {
|
||||
switch ci.content {
|
||||
case let .rcvChatFeature(feature, enabled, param): FeatureInfo(feature, enabled.iconColor, param)
|
||||
case let .sndChatFeature(feature, enabled, param): FeatureInfo(feature, enabled.iconColor, param)
|
||||
case let .rcvGroupFeature(feature, preference, param): FeatureInfo(feature, preference.enable.iconColor, param)
|
||||
case let .sndGroupFeature(feature, preference, param): FeatureInfo(feature, preference.enable.iconColor, param)
|
||||
default: nil
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func featureIconView(_ f: FeatureInfo) -> some View {
|
||||
let i = Image(systemName: f.icon)
|
||||
.foregroundColor(f.color)
|
||||
.scaleEffect(f.scale)
|
||||
if let param = f.param {
|
||||
HStack {
|
||||
i
|
||||
chatEventText(Text(param)).lineLimit(1)
|
||||
}
|
||||
} else {
|
||||
i
|
||||
}
|
||||
}
|
||||
|
||||
private var fullFeatureView: some View {
|
||||
HStack(alignment: .bottom, spacing: 4) {
|
||||
Image(systemName: icon ?? feature.iconFilled)
|
||||
.foregroundColor(iconColor)
|
||||
.scaleEffect(feature.iconScale)
|
||||
chatEventText(chatItem)
|
||||
}
|
||||
.padding(.leading, 6)
|
||||
.padding(.bottom, 6)
|
||||
.padding(.horizontal, 6)
|
||||
.padding(.vertical, 4)
|
||||
.textSelection(.disabled)
|
||||
}
|
||||
}
|
||||
@@ -31,6 +103,6 @@ struct CIChatFeatureView: View {
|
||||
struct CIChatFeatureView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let enabled = FeatureEnabled(forUser: false, forContact: false)
|
||||
CIChatFeatureView(chatItem: ChatItem.getChatFeatureSample(.fullDelete, enabled), feature: ChatFeature.fullDelete, iconColor: enabled.iconColor)
|
||||
CIChatFeatureView(chatItem: ChatItem.getChatFeatureSample(.fullDelete, enabled), revealed: Binding.constant(true), feature: ChatFeature.fullDelete, iconColor: enabled.iconColor)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,9 @@ struct CIEventView: View {
|
||||
var eventText: Text
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .bottom, spacing: 0) {
|
||||
eventText
|
||||
}
|
||||
.padding(.leading, 6)
|
||||
.padding(.bottom, 6)
|
||||
eventText
|
||||
.padding(.horizontal, 6)
|
||||
.padding(.vertical, 4)
|
||||
.textSelection(.disabled)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct CIFeaturePreferenceView: View {
|
||||
@EnvironmentObject var chat: Chat
|
||||
@ObservedObject var chat: Chat
|
||||
var chatItem: ChatItem
|
||||
var feature: ChatFeature
|
||||
var allowed: FeatureAllowed
|
||||
@@ -80,7 +80,6 @@ struct CIFeaturePreferenceView_Previews: PreviewProvider {
|
||||
quotedItem: nil,
|
||||
file: nil
|
||||
)
|
||||
CIFeaturePreferenceView(chatItem: chatItem, feature: ChatFeature.timedMessages, allowed: .yes, param: 30)
|
||||
.environmentObject(Chat.sampleData)
|
||||
CIFeaturePreferenceView(chat: Chat.sampleData, chatItem: chatItem, feature: ChatFeature.timedMessages, allowed: .yes, param: 30)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct CIFileView: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
let file: CIFile?
|
||||
let edited: Bool
|
||||
@@ -83,7 +84,7 @@ struct CIFileView: View {
|
||||
if fileSizeValid() {
|
||||
Task {
|
||||
logger.debug("CIFileView fileAction - in .rcvInvitation, in Task")
|
||||
if let user = ChatModel.shared.currentUser {
|
||||
if let user = m.currentUser {
|
||||
let encrypted = privacyEncryptLocalFilesGroupDefault.get()
|
||||
await receiveFile(user: user, fileId: file.fileId, encrypted: encrypted)
|
||||
}
|
||||
@@ -234,18 +235,17 @@ struct CIFileView_Previews: PreviewProvider {
|
||||
file: nil
|
||||
)
|
||||
Group {
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: sentFile, revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(fileName: "some_long_file_name_here", fileStatus: .rcvInvitation), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvAccepted), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvCancelled), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(fileSize: 1_000_000_000, fileStatus: .rcvInvitation), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(text: "Hello there", fileStatus: .rcvInvitation), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", fileStatus: .rcvInvitation), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: fileChatItemWtFile, revealed: Binding.constant(false))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: sentFile, revealed: Binding.constant(false))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(), revealed: Binding.constant(false))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileName: "some_long_file_name_here", fileStatus: .rcvInvitation), revealed: Binding.constant(false))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvAccepted), revealed: Binding.constant(false))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), revealed: Binding.constant(false))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvCancelled), revealed: Binding.constant(false))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileSize: 1_000_000_000, fileStatus: .rcvInvitation), revealed: Binding.constant(false))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(text: "Hello there", fileStatus: .rcvInvitation), revealed: Binding.constant(false))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", fileStatus: .rcvInvitation), revealed: Binding.constant(false))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: fileChatItemWtFile, revealed: Binding.constant(false))
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 360))
|
||||
.environmentObject(Chat.sampleData)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,34 +17,45 @@ struct CIGroupInvitationView: View {
|
||||
var memberRole: GroupMemberRole
|
||||
var chatIncognito: Bool = false
|
||||
@State private var frameWidth: CGFloat = 0
|
||||
@State private var inProgress = false
|
||||
@State private var progressByTimeout = false
|
||||
|
||||
var body: some View {
|
||||
let action = !chatItem.chatDir.sent && groupInvitation.status == .pending
|
||||
let v = ZStack(alignment: .bottomTrailing) {
|
||||
VStack(alignment: .leading) {
|
||||
groupInfoView(action)
|
||||
.padding(.horizontal, 2)
|
||||
.padding(.top, 8)
|
||||
.padding(.bottom, 6)
|
||||
.overlay(DetermineWidth())
|
||||
ZStack {
|
||||
VStack(alignment: .leading) {
|
||||
groupInfoView(action)
|
||||
.padding(.horizontal, 2)
|
||||
.padding(.top, 8)
|
||||
.padding(.bottom, 6)
|
||||
.overlay(DetermineWidth())
|
||||
|
||||
Divider().frame(width: frameWidth)
|
||||
Divider().frame(width: frameWidth)
|
||||
|
||||
if action {
|
||||
groupInvitationText()
|
||||
.overlay(DetermineWidth())
|
||||
Text(chatIncognito ? "Tap to join incognito" : "Tap to join")
|
||||
.foregroundColor(chatIncognito ? .indigo : .accentColor)
|
||||
.font(.callout)
|
||||
.padding(.trailing, 60)
|
||||
.overlay(DetermineWidth())
|
||||
} else {
|
||||
groupInvitationText()
|
||||
.padding(.trailing, 60)
|
||||
.overlay(DetermineWidth())
|
||||
if action {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
groupInvitationText()
|
||||
.overlay(DetermineWidth())
|
||||
Text(chatIncognito ? "Tap to join incognito" : "Tap to join")
|
||||
.foregroundColor(inProgress ? .secondary : chatIncognito ? .indigo : .accentColor)
|
||||
.font(.callout)
|
||||
.padding(.trailing, 60)
|
||||
.overlay(DetermineWidth())
|
||||
}
|
||||
} else {
|
||||
groupInvitationText()
|
||||
.padding(.trailing, 60)
|
||||
.overlay(DetermineWidth())
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 2)
|
||||
|
||||
if progressByTimeout {
|
||||
ProgressView().scaleEffect(2)
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 2)
|
||||
|
||||
chatItem.timestampText
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
@@ -55,11 +66,24 @@ struct CIGroupInvitationView: View {
|
||||
.cornerRadius(18)
|
||||
.textSelection(.disabled)
|
||||
.onPreferenceChange(DetermineWidth.Key.self) { frameWidth = $0 }
|
||||
.onChange(of: inProgress) { inProgress in
|
||||
if inProgress {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
progressByTimeout = inProgress
|
||||
}
|
||||
} else {
|
||||
progressByTimeout = false
|
||||
}
|
||||
}
|
||||
|
||||
if action {
|
||||
v.onTapGesture {
|
||||
joinGroup(groupInvitation.groupId)
|
||||
inProgress = true
|
||||
joinGroup(groupInvitation.groupId) {
|
||||
await MainActor.run { inProgress = false }
|
||||
}
|
||||
}
|
||||
.disabled(inProgress)
|
||||
} else {
|
||||
v
|
||||
}
|
||||
@@ -67,7 +91,7 @@ struct CIGroupInvitationView: View {
|
||||
|
||||
private func groupInfoView(_ action: Bool) -> some View {
|
||||
var color: Color
|
||||
if action {
|
||||
if action && !inProgress {
|
||||
color = chatIncognito ? .indigo : .accentColor
|
||||
} else {
|
||||
color = Color(uiColor: .tertiaryLabel)
|
||||
|
||||
@@ -10,6 +10,7 @@ import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct CIImageView: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
let chatItem: ChatItem
|
||||
let image: String
|
||||
@@ -36,7 +37,7 @@ struct CIImageView: View {
|
||||
switch file.fileStatus {
|
||||
case .rcvInvitation:
|
||||
Task {
|
||||
if let user = ChatModel.shared.currentUser {
|
||||
if let user = m.currentUser {
|
||||
await receiveFile(user: user, fileId: file.fileId, encrypted: chatItem.encryptLocalFile)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct CIMemberCreatedContactView: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
var chatItem: ChatItem
|
||||
|
||||
var body: some View {
|
||||
@@ -21,7 +22,7 @@ struct CIMemberCreatedContactView: View {
|
||||
.onTapGesture {
|
||||
dismissAllSheets(animated: true)
|
||||
DispatchQueue.main.async {
|
||||
ChatModel.shared.chatId = "@\(contactId)"
|
||||
m.chatId = "@\(contactId)"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -10,7 +10,7 @@ import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct CIMetaView: View {
|
||||
@EnvironmentObject var chat: Chat
|
||||
@ObservedObject var chat: Chat
|
||||
var chatItem: ChatItem
|
||||
var metaColor = Color.secondary
|
||||
var paleMetaColor = Color(UIColor.tertiaryLabel)
|
||||
@@ -95,15 +95,14 @@ private func statusIconText(_ icon: String, _ color: Color) -> Text {
|
||||
struct CIMetaView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
CIMetaView(chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete)))
|
||||
CIMetaView(chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .partial)))
|
||||
CIMetaView(chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndRcvd(msgRcptStatus: .ok, sndProgress: .complete)))
|
||||
CIMetaView(chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndRcvd(msgRcptStatus: .ok, sndProgress: .partial)))
|
||||
CIMetaView(chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndRcvd(msgRcptStatus: .badMsgHash, sndProgress: .complete)))
|
||||
CIMetaView(chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), itemEdited: true))
|
||||
CIMetaView(chatItem: ChatItem.getDeletedContentSample())
|
||||
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete)))
|
||||
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .partial)))
|
||||
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndRcvd(msgRcptStatus: .ok, sndProgress: .complete)))
|
||||
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndRcvd(msgRcptStatus: .ok, sndProgress: .partial)))
|
||||
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndRcvd(msgRcptStatus: .badMsgHash, sndProgress: .complete)))
|
||||
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), itemEdited: true))
|
||||
CIMetaView(chat: Chat.sampleData, chatItem: ChatItem.getDeletedContentSample())
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 100))
|
||||
.environmentObject(Chat.sampleData)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ import SimpleXChat
|
||||
let decryptErrorReason: LocalizedStringKey = "It can happen when you or your connection used the old database backup."
|
||||
|
||||
struct CIRcvDecryptionError: View {
|
||||
@EnvironmentObject var chat: Chat
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@ObservedObject var chat: Chat
|
||||
var msgDecryptError: MsgDecryptError
|
||||
var msgCount: UInt32
|
||||
var chatItem: ChatItem
|
||||
@@ -45,7 +46,7 @@ struct CIRcvDecryptionError: View {
|
||||
do {
|
||||
let (member, stats) = try apiGroupMemberInfo(groupInfo.apiId, groupMember.groupMemberId)
|
||||
if let s = stats {
|
||||
ChatModel.shared.updateGroupMemberConnectionStats(groupInfo, member, s)
|
||||
m.updateGroupMemberConnectionStats(groupInfo, member, s)
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("apiGroupMemberInfo error: \(responseError(error))")
|
||||
@@ -65,7 +66,7 @@ struct CIRcvDecryptionError: View {
|
||||
|
||||
@ViewBuilder private func viewBody() -> some View {
|
||||
if case let .direct(contact) = chat.chatInfo,
|
||||
let contactStats = contact.activeConn.connectionStats {
|
||||
let contactStats = contact.activeConn?.connectionStats {
|
||||
if contactStats.ratchetSyncAllowed {
|
||||
decryptionErrorItemFixButton(syncSupported: true) {
|
||||
alert = .syncAllowedAlert { syncContactConnection(contact) }
|
||||
@@ -79,8 +80,8 @@ struct CIRcvDecryptionError: View {
|
||||
}
|
||||
} else if case let .group(groupInfo) = chat.chatInfo,
|
||||
case let .groupRcv(groupMember) = chatItem.chatDir,
|
||||
let modelMember = ChatModel.shared.groupMembers.first(where: { $0.id == groupMember.id }),
|
||||
let memberStats = modelMember.activeConn?.connectionStats {
|
||||
let mem = m.getGroupMember(groupMember.groupMemberId),
|
||||
let memberStats = mem.wrapped.activeConn?.connectionStats {
|
||||
if memberStats.ratchetSyncAllowed {
|
||||
decryptionErrorItemFixButton(syncSupported: true) {
|
||||
alert = .syncAllowedAlert { syncMemberConnection(groupInfo, groupMember) }
|
||||
@@ -122,7 +123,7 @@ struct CIRcvDecryptionError: View {
|
||||
)
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
CIMetaView(chatItem: chatItem)
|
||||
CIMetaView(chat: chat, chatItem: chatItem)
|
||||
.padding(.horizontal, 12)
|
||||
}
|
||||
.onTapGesture(perform: { onClick() })
|
||||
@@ -142,7 +143,7 @@ struct CIRcvDecryptionError: View {
|
||||
+ ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true)
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
CIMetaView(chatItem: chatItem)
|
||||
CIMetaView(chat: chat, chatItem: chatItem)
|
||||
.padding(.horizontal, 12)
|
||||
}
|
||||
.onTapGesture(perform: { onClick() })
|
||||
@@ -164,6 +165,8 @@ struct CIRcvDecryptionError: View {
|
||||
message = Text("\(msgCount) messages failed to decrypt.") + Text("\n") + why
|
||||
case .other:
|
||||
message = Text("\(msgCount) messages failed to decrypt.") + Text("\n") + why
|
||||
case .ratchetSync:
|
||||
message = Text("Encryption re-negotiation failed.")
|
||||
}
|
||||
return message
|
||||
}
|
||||
@@ -173,7 +176,7 @@ struct CIRcvDecryptionError: View {
|
||||
do {
|
||||
let (mem, stats) = try apiSyncGroupMemberRatchet(groupInfo.apiId, member.groupMemberId, false)
|
||||
await MainActor.run {
|
||||
ChatModel.shared.updateGroupMemberConnectionStats(groupInfo, mem, stats)
|
||||
m.updateGroupMemberConnectionStats(groupInfo, mem, stats)
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("syncMemberConnection apiSyncGroupMemberRatchet error: \(responseError(error))")
|
||||
@@ -190,7 +193,7 @@ struct CIRcvDecryptionError: View {
|
||||
do {
|
||||
let stats = try apiSyncContactRatchet(contact.apiId, false)
|
||||
await MainActor.run {
|
||||
ChatModel.shared.updateContactConnectionStats(contact, stats)
|
||||
m.updateContactConnectionStats(contact, stats)
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("syncContactConnection apiSyncContactRatchet error: \(responseError(error))")
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
import SwiftUI
|
||||
import AVKit
|
||||
import SimpleXChat
|
||||
import Combine
|
||||
|
||||
struct CIVideoView: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
private let chatItem: ChatItem
|
||||
private let image: String
|
||||
@@ -27,6 +29,7 @@ struct CIVideoView: View {
|
||||
@State private var showFullScreenPlayer = false
|
||||
@State private var timeObserver: Any? = nil
|
||||
@State private var fullScreenTimeObserver: Any? = nil
|
||||
@State private var publisher: AnyCancellable? = nil
|
||||
|
||||
init(chatItem: ChatItem, image: String, duration: Int, maxWidth: CGFloat, videoWidth: Binding<CGFloat?>, scrollProxy: ScrollViewProxy?) {
|
||||
self.chatItem = chatItem
|
||||
@@ -101,7 +104,7 @@ struct CIVideoView: View {
|
||||
let canBePlayed = !chatItem.chatDir.sent || file.fileStatus == CIFileStatus.sndComplete
|
||||
VideoPlayerView(player: player, url: url, showControls: false)
|
||||
.frame(width: w, height: w * preview.size.height / preview.size.width)
|
||||
.onChange(of: ChatModel.shared.stopPreviousRecPlay) { playingUrl in
|
||||
.onChange(of: m.stopPreviousRecPlay) { playingUrl in
|
||||
if playingUrl != url {
|
||||
player.pause()
|
||||
videoPlaying = false
|
||||
@@ -124,7 +127,7 @@ struct CIVideoView: View {
|
||||
}
|
||||
if !videoPlaying {
|
||||
Button {
|
||||
ChatModel.shared.stopPreviousRecPlay = url
|
||||
m.stopPreviousRecPlay = url
|
||||
player.play()
|
||||
} label: {
|
||||
playPauseIcon(canBePlayed ? "play.fill" : "play.slash")
|
||||
@@ -256,7 +259,7 @@ 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) {
|
||||
Task {
|
||||
if let user = ChatModel.shared.currentUser {
|
||||
if let user = m.currentUser {
|
||||
await receiveFile(user, file.fileId, encrypted, false)
|
||||
}
|
||||
}
|
||||
@@ -290,9 +293,17 @@ struct CIVideoView: View {
|
||||
)
|
||||
.onAppear {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now()) {
|
||||
ChatModel.shared.stopPreviousRecPlay = url
|
||||
m.stopPreviousRecPlay = url
|
||||
if let player = fullPlayer {
|
||||
player.play()
|
||||
var played = false
|
||||
publisher = player.publisher(for: \.timeControlStatus).sink { status in
|
||||
if played || status == .playing {
|
||||
AppDelegate.keepScreenOn(status == .playing)
|
||||
AudioPlayer.changeAudioSession(status == .playing)
|
||||
}
|
||||
played = status == .playing
|
||||
}
|
||||
fullScreenTimeObserver = NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player.currentItem, queue: .main) { _ in
|
||||
player.seek(to: CMTime.zero)
|
||||
player.play()
|
||||
@@ -307,6 +318,7 @@ struct CIVideoView: View {
|
||||
fullScreenTimeObserver = nil
|
||||
fullPlayer?.pause()
|
||||
fullPlayer?.seek(to: CMTime.zero)
|
||||
publisher?.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct CIVoiceView: View {
|
||||
@ObservedObject var chat: Chat
|
||||
var chatItem: ChatItem
|
||||
let recordingFile: CIFile?
|
||||
let duration: Int
|
||||
@@ -91,7 +92,7 @@ struct CIVoiceView: View {
|
||||
}
|
||||
|
||||
private func metaView() -> some View {
|
||||
CIMetaView(chatItem: chatItem)
|
||||
CIMetaView(chat: chat, chatItem: chatItem)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +220,7 @@ struct VoiceMessagePlayer: View {
|
||||
private func downloadButton(_ recordingFile: CIFile) -> some View {
|
||||
Button {
|
||||
Task {
|
||||
if let user = ChatModel.shared.currentUser {
|
||||
if let user = chatModel.currentUser {
|
||||
await receiveFile(user: user, fileId: recordingFile.fileId, encrypted: privacyEncryptLocalFilesGroupDefault.get())
|
||||
}
|
||||
}
|
||||
@@ -284,6 +285,7 @@ struct CIVoiceView_Previews: PreviewProvider {
|
||||
)
|
||||
Group {
|
||||
CIVoiceView(
|
||||
chat: Chat.sampleData,
|
||||
chatItem: ChatItem.getVoiceMsgContentSample(),
|
||||
recordingFile: CIFile.getSample(fileName: "voice.m4a", fileSize: 65536, fileStatus: .rcvComplete),
|
||||
duration: 30,
|
||||
@@ -292,12 +294,11 @@ struct CIVoiceView_Previews: PreviewProvider {
|
||||
playbackTime: .constant(TimeInterval(20)),
|
||||
allowMenu: Binding.constant(true)
|
||||
)
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: sentVoiceMessage, revealed: Binding.constant(false), allowMenu: .constant(true), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(), revealed: Binding.constant(false), allowMenu: .constant(true), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), revealed: Binding.constant(false), allowMenu: .constant(true), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: voiceMessageWtFile, revealed: Binding.constant(false), allowMenu: .constant(true), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage, revealed: Binding.constant(false), allowMenu: .constant(true), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(), revealed: Binding.constant(false), allowMenu: .constant(true), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), revealed: Binding.constant(false), allowMenu: .constant(true), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: voiceMessageWtFile, revealed: Binding.constant(false), allowMenu: .constant(true), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 360))
|
||||
.environmentObject(Chat.sampleData)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import SimpleXChat
|
||||
|
||||
struct DeletedItemView: View {
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@ObservedObject var chat: Chat
|
||||
var chatItem: ChatItem
|
||||
|
||||
var body: some View {
|
||||
@@ -18,7 +19,7 @@ struct DeletedItemView: View {
|
||||
Text(chatItem.content.text)
|
||||
.foregroundColor(.secondary)
|
||||
.italic()
|
||||
CIMetaView(chatItem: chatItem)
|
||||
CIMetaView(chat: chat, chatItem: chatItem)
|
||||
.padding(.horizontal, 12)
|
||||
}
|
||||
.padding(.leading, 12)
|
||||
@@ -32,8 +33,8 @@ struct DeletedItemView: View {
|
||||
struct DeletedItemView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
DeletedItemView(chatItem: ChatItem.getDeletedContentSample())
|
||||
DeletedItemView(chatItem: ChatItem.getDeletedContentSample(dir: .groupRcv(groupMember: GroupMember.sampleData)))
|
||||
DeletedItemView(chat: Chat.sampleData, chatItem: ChatItem.getDeletedContentSample())
|
||||
DeletedItemView(chat: Chat.sampleData, chatItem: ChatItem.getDeletedContentSample(dir: .groupRcv(groupMember: GroupMember.sampleData)))
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 200))
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct EmojiItemView: View {
|
||||
@ObservedObject var chat: Chat
|
||||
var chatItem: ChatItem
|
||||
|
||||
var body: some View {
|
||||
@@ -17,7 +18,7 @@ struct EmojiItemView: View {
|
||||
emojiText(chatItem.content.text)
|
||||
.padding(.top, 8)
|
||||
.padding(.horizontal, 6)
|
||||
CIMetaView(chatItem: chatItem)
|
||||
CIMetaView(chat: chat, chatItem: chatItem)
|
||||
.padding(.bottom, 8)
|
||||
.padding(.horizontal, 12)
|
||||
}
|
||||
@@ -32,8 +33,8 @@ func emojiText(_ text: String) -> Text {
|
||||
struct EmojiItemView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group{
|
||||
EmojiItemView(chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂", .sndSent(sndProgress: .complete)))
|
||||
EmojiItemView(chatItem: ChatItem.getSample(2, .directRcv, .now, "👍"))
|
||||
EmojiItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂", .sndSent(sndProgress: .complete)))
|
||||
EmojiItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "👍"))
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 70))
|
||||
}
|
||||
|
||||
@@ -88,13 +88,12 @@ struct FramedCIVoiceView_Previews: PreviewProvider {
|
||||
file: CIFile.getSample(fileStatus: .sndComplete)
|
||||
)
|
||||
Group {
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: sentVoiceMessage, revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there"), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there", fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: voiceMessageWithQuote, revealed: Binding.constant(false))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage, revealed: Binding.constant(false))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there"), revealed: Binding.constant(false))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there", fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), revealed: Binding.constant(false))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."), revealed: Binding.constant(false))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: voiceMessageWithQuote, revealed: Binding.constant(false))
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 360))
|
||||
.environmentObject(Chat.sampleData)
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -150,7 +150,7 @@ struct FullScreenMediaView: View {
|
||||
|
||||
private func startPlayerAndNotify() {
|
||||
if let player = player {
|
||||
ChatModel.shared.stopPreviousRecPlay = url
|
||||
m.stopPreviousRecPlay = url
|
||||
player.play()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,12 @@ import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct IntegrityErrorItemView: View {
|
||||
@ObservedObject var chat: Chat
|
||||
var msgError: MsgErrorType
|
||||
var chatItem: ChatItem
|
||||
|
||||
var body: some View {
|
||||
CIMsgError(chatItem: chatItem) {
|
||||
CIMsgError(chat: chat, chatItem: chatItem) {
|
||||
switch msgError {
|
||||
case .msgSkipped:
|
||||
AlertManager.shared.showAlertMsg(
|
||||
@@ -52,6 +53,7 @@ struct IntegrityErrorItemView: View {
|
||||
}
|
||||
|
||||
struct CIMsgError: View {
|
||||
@ObservedObject var chat: Chat
|
||||
var chatItem: ChatItem
|
||||
var onTap: () -> Void
|
||||
|
||||
@@ -60,7 +62,7 @@ struct CIMsgError: View {
|
||||
Text(chatItem.content.text)
|
||||
.foregroundColor(.red)
|
||||
.italic()
|
||||
CIMetaView(chatItem: chatItem)
|
||||
CIMetaView(chat: chat, chatItem: chatItem)
|
||||
.padding(.horizontal, 12)
|
||||
}
|
||||
.padding(.leading, 12)
|
||||
@@ -74,6 +76,6 @@ struct CIMsgError: View {
|
||||
|
||||
struct IntegrityErrorItemView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
IntegrityErrorItemView(msgError: .msgBadHash, chatItem: ChatItem.getIntegrityErrorSample())
|
||||
IntegrityErrorItemView(chat: Chat.sampleData, msgError: .msgBadHash, chatItem: ChatItem.getIntegrityErrorSample())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,39 +10,70 @@ import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct MarkedDeletedItemView: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@ObservedObject var chat: Chat
|
||||
var chatItem: ChatItem
|
||||
@Binding var revealed: Bool
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .bottom, spacing: 0) {
|
||||
if case let .moderated(_, byGroupMember) = chatItem.meta.itemDeleted {
|
||||
markedDeletedText("moderated by \(byGroupMember.chatViewName)")
|
||||
} else {
|
||||
markedDeletedText("marked deleted")
|
||||
}
|
||||
CIMetaView(chatItem: chatItem)
|
||||
.padding(.horizontal, 12)
|
||||
}
|
||||
.padding(.leading, 12)
|
||||
(Text(mergedMarkedDeletedText).italic() + Text(" ") + chatItem.timestampText)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 6)
|
||||
.background(chatItemFrameColor(chatItem, colorScheme))
|
||||
.cornerRadius(18)
|
||||
.textSelection(.disabled)
|
||||
}
|
||||
|
||||
func markedDeletedText(_ s: LocalizedStringKey) -> some View {
|
||||
Text(s)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.italic()
|
||||
.lineLimit(1)
|
||||
var mergedMarkedDeletedText: LocalizedStringKey {
|
||||
if !revealed,
|
||||
let ciCategory = chatItem.mergeCategory,
|
||||
var i = m.getChatItemIndex(chatItem) {
|
||||
var moderated = 0
|
||||
var blocked = 0
|
||||
var deleted = 0
|
||||
var moderatedBy: Set<String> = []
|
||||
while i < m.reversedChatItems.count,
|
||||
let ci = .some(m.reversedChatItems[i]),
|
||||
ci.mergeCategory == ciCategory,
|
||||
let itemDeleted = ci.meta.itemDeleted {
|
||||
switch itemDeleted {
|
||||
case let .moderated(_, byGroupMember):
|
||||
moderated += 1
|
||||
moderatedBy.insert(byGroupMember.displayName)
|
||||
case .blocked: blocked += 1
|
||||
case .deleted: deleted += 1
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
let total = moderated + blocked + deleted
|
||||
return total <= 1
|
||||
? markedDeletedText
|
||||
: total == moderated
|
||||
? "\(total) messages moderated by \(moderatedBy.joined(separator: ", "))"
|
||||
: total == blocked
|
||||
? "\(total) messages blocked"
|
||||
: "\(total) messages marked deleted"
|
||||
} else {
|
||||
return markedDeletedText
|
||||
}
|
||||
}
|
||||
|
||||
var markedDeletedText: LocalizedStringKey {
|
||||
switch chatItem.meta.itemDeleted {
|
||||
case let .moderated(_, byGroupMember): "moderated by \(byGroupMember.displayName)"
|
||||
case .blocked: "blocked"
|
||||
default: "marked deleted"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MarkedDeletedItemView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
MarkedDeletedItemView(chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)))
|
||||
MarkedDeletedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true))
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 200))
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ private func typing(_ w: Font.Weight = .light) -> Text {
|
||||
}
|
||||
|
||||
struct MsgContentView: View {
|
||||
@EnvironmentObject var chat: Chat
|
||||
@ObservedObject var chat: Chat
|
||||
var text: String
|
||||
var formattedText: [FormattedText]? = nil
|
||||
var sender: String? = nil
|
||||
@@ -152,6 +152,7 @@ struct MsgContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let chatItem = ChatItem.getSample(1, .directSnd, .now, "hello")
|
||||
return MsgContentView(
|
||||
chat: Chat.sampleData,
|
||||
text: chatItem.text,
|
||||
formattedText: chatItem.formattedText,
|
||||
sender: chatItem.memberDisplayName,
|
||||
|
||||
@@ -10,6 +10,7 @@ import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct ChatItemInfoView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
var ci: ChatItem
|
||||
@Binding var chatItemInfo: ChatItemInfo?
|
||||
@@ -290,8 +291,8 @@ struct ChatItemInfoView: View {
|
||||
|
||||
private func membersStatuses(_ memberDeliveryStatuses: [MemberDeliveryStatus]) -> [(GroupMember, CIStatus)] {
|
||||
memberDeliveryStatuses.compactMap({ mds in
|
||||
if let mem = ChatModel.shared.groupMembers.first(where: { $0.groupMemberId == mds.groupMemberId }) {
|
||||
return (mem, mds.memberDeliveryStatus)
|
||||
if let mem = chatModel.getGroupMember(mds.groupMemberId) {
|
||||
return (mem.wrapped, mds.memberDeliveryStatus)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct ChatItemView: View {
|
||||
var chatInfo: ChatInfo
|
||||
@ObservedObject var chat: Chat
|
||||
var chatItem: ChatItem
|
||||
var maxWidth: CGFloat = .infinity
|
||||
@State var scrollProxy: ScrollViewProxy? = nil
|
||||
@@ -19,8 +19,19 @@ struct ChatItemView: View {
|
||||
@Binding var audioPlayer: AudioPlayer?
|
||||
@Binding var playbackState: VoiceMessagePlaybackState
|
||||
@Binding var playbackTime: TimeInterval?
|
||||
init(chatInfo: ChatInfo, chatItem: ChatItem, showMember: Bool = false, maxWidth: CGFloat = .infinity, scrollProxy: ScrollViewProxy? = nil, revealed: Binding<Bool>, allowMenu: Binding<Bool> = .constant(false), audioPlayer: Binding<AudioPlayer?> = .constant(nil), playbackState: Binding<VoiceMessagePlaybackState> = .constant(.noPlayback), playbackTime: Binding<TimeInterval?> = .constant(nil)) {
|
||||
self.chatInfo = chatInfo
|
||||
init(
|
||||
chat: Chat,
|
||||
chatItem: ChatItem,
|
||||
showMember: Bool = false,
|
||||
maxWidth: CGFloat = .infinity,
|
||||
scrollProxy: ScrollViewProxy? = nil,
|
||||
revealed: Binding<Bool>,
|
||||
allowMenu: Binding<Bool> = .constant(false),
|
||||
audioPlayer: Binding<AudioPlayer?> = .constant(nil),
|
||||
playbackState: Binding<VoiceMessagePlaybackState> = .constant(.noPlayback),
|
||||
playbackTime: Binding<TimeInterval?> = .constant(nil)
|
||||
) {
|
||||
self.chat = chat
|
||||
self.chatItem = chatItem
|
||||
self.maxWidth = maxWidth
|
||||
_scrollProxy = .init(initialValue: scrollProxy)
|
||||
@@ -33,15 +44,15 @@ struct ChatItemView: View {
|
||||
|
||||
var body: some View {
|
||||
let ci = chatItem
|
||||
if chatItem.meta.itemDeleted != nil && !revealed {
|
||||
MarkedDeletedItemView(chatItem: chatItem)
|
||||
if chatItem.meta.itemDeleted != nil && (!revealed || chatItem.isDeletedContent) {
|
||||
MarkedDeletedItemView(chat: chat, chatItem: chatItem, revealed: $revealed)
|
||||
} else if ci.quotedItem == nil && ci.meta.itemDeleted == nil && !ci.meta.isLive {
|
||||
if let mc = ci.content.msgContent, mc.isText && isShortEmoji(ci.content.text) {
|
||||
EmojiItemView(chatItem: ci)
|
||||
EmojiItemView(chat: chat, chatItem: ci)
|
||||
} else if ci.content.text.isEmpty, case let .voice(_, duration) = ci.content.msgContent {
|
||||
CIVoiceView(chatItem: ci, recordingFile: ci.file, duration: duration, audioPlayer: $audioPlayer, playbackState: $playbackState, playbackTime: $playbackTime, allowMenu: $allowMenu)
|
||||
CIVoiceView(chat: chat, chatItem: ci, recordingFile: ci.file, duration: duration, audioPlayer: $audioPlayer, playbackState: $playbackState, playbackTime: $playbackTime, allowMenu: $allowMenu)
|
||||
} else if ci.content.msgContent == nil {
|
||||
ChatItemContentView(chatInfo: chatInfo, chatItem: chatItem, msgContentView: { Text(ci.text) }) // msgContent is unreachable branch in this case
|
||||
ChatItemContentView(chat: chat, chatItem: chatItem, revealed: $revealed, msgContentView: { Text(ci.text) }) // msgContent is unreachable branch in this case
|
||||
} else {
|
||||
framedItemView()
|
||||
}
|
||||
@@ -51,14 +62,15 @@ struct ChatItemView: View {
|
||||
}
|
||||
|
||||
private func framedItemView() -> some View {
|
||||
FramedItemView(chatInfo: chatInfo, chatItem: chatItem, maxWidth: maxWidth, scrollProxy: scrollProxy, allowMenu: $allowMenu, audioPlayer: $audioPlayer, playbackState: $playbackState, playbackTime: $playbackTime)
|
||||
FramedItemView(chat: chat, chatItem: chatItem, revealed: $revealed, maxWidth: maxWidth, scrollProxy: scrollProxy, allowMenu: $allowMenu, audioPlayer: $audioPlayer, playbackState: $playbackState, playbackTime: $playbackTime)
|
||||
}
|
||||
}
|
||||
|
||||
struct ChatItemContentView<Content: View>: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
var chatInfo: ChatInfo
|
||||
@ObservedObject var chat: Chat
|
||||
var chatItem: ChatItem
|
||||
@Binding var revealed: Bool
|
||||
var msgContentView: () -> Content
|
||||
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
|
||||
|
||||
@@ -72,15 +84,14 @@ struct ChatItemContentView<Content: View>: View {
|
||||
case let .rcvCall(status, duration): callItemView(status, duration)
|
||||
case let .rcvIntegrityError(msgError):
|
||||
if developerTools {
|
||||
IntegrityErrorItemView(msgError: msgError, chatItem: chatItem)
|
||||
IntegrityErrorItemView(chat: chat, msgError: msgError, chatItem: chatItem)
|
||||
} else {
|
||||
ZStack {}
|
||||
}
|
||||
case let .rcvDecryptionError(msgDecryptError, msgCount): CIRcvDecryptionError(msgDecryptError: msgDecryptError, msgCount: msgCount, chatItem: chatItem)
|
||||
case let .rcvDecryptionError(msgDecryptError, msgCount): CIRcvDecryptionError(chat: chat, msgDecryptError: msgDecryptError, msgCount: msgCount, chatItem: chatItem)
|
||||
case let .rcvGroupInvitation(groupInvitation, memberRole): groupInvitationItemView(groupInvitation, memberRole)
|
||||
case let .sndGroupInvitation(groupInvitation, memberRole): groupInvitationItemView(groupInvitation, memberRole)
|
||||
case .rcvDirectEvent: eventItemView()
|
||||
case .rcvGroupEvent(.memberConnected): CIEventView(eventText: membersConnectedItemText)
|
||||
case .rcvGroupEvent(.memberCreatedContact): CIMemberCreatedContactView(chatItem: chatItem)
|
||||
case .rcvGroupEvent: eventItemView()
|
||||
case .sndGroupEvent: eventItemView()
|
||||
@@ -89,9 +100,9 @@ struct ChatItemContentView<Content: View>: View {
|
||||
case let .rcvChatFeature(feature, enabled, _): chatFeatureView(feature, enabled.iconColor)
|
||||
case let .sndChatFeature(feature, enabled, _): chatFeatureView(feature, enabled.iconColor)
|
||||
case let .rcvChatPreference(feature, allowed, param):
|
||||
CIFeaturePreferenceView(chatItem: chatItem, feature: feature, allowed: allowed, param: param)
|
||||
CIFeaturePreferenceView(chat: chat, chatItem: chatItem, feature: feature, allowed: allowed, param: param)
|
||||
case let .sndChatPreference(feature, _, _):
|
||||
CIChatFeatureView(chatItem: chatItem, feature: feature, icon: feature.icon, iconColor: .secondary)
|
||||
CIChatFeatureView(chatItem: chatItem, revealed: $revealed, feature: feature, icon: feature.icon, iconColor: .secondary)
|
||||
case let .rcvGroupFeature(feature, preference, _): chatFeatureView(feature, preference.enable.iconColor)
|
||||
case let .sndGroupFeature(feature, preference, _): chatFeatureView(feature, preference.enable.iconColor)
|
||||
case let .rcvChatFeatureRejected(feature): chatFeatureView(feature, .red)
|
||||
@@ -103,15 +114,15 @@ struct ChatItemContentView<Content: View>: View {
|
||||
}
|
||||
|
||||
private func deletedItemView() -> some View {
|
||||
DeletedItemView(chatItem: chatItem)
|
||||
DeletedItemView(chat: chat, chatItem: chatItem)
|
||||
}
|
||||
|
||||
private func callItemView(_ status: CICallStatus, _ duration: Int) -> some View {
|
||||
CICallItemView(chatInfo: chatInfo, chatItem: chatItem, status: status, duration: duration)
|
||||
CICallItemView(chat: chat, chatItem: chatItem, status: status, duration: duration)
|
||||
}
|
||||
|
||||
private func groupInvitationItemView(_ groupInvitation: CIGroupInvitation, _ memberRole: GroupMemberRole) -> some View {
|
||||
CIGroupInvitationView(chatItem: chatItem, groupInvitation: groupInvitation, memberRole: memberRole, chatIncognito: chatInfo.incognito)
|
||||
CIGroupInvitationView(chatItem: chatItem, groupInvitation: groupInvitation, memberRole: memberRole, chatIncognito: chat.chatInfo.incognito)
|
||||
}
|
||||
|
||||
private func eventItemView() -> some View {
|
||||
@@ -119,7 +130,9 @@ struct ChatItemContentView<Content: View>: View {
|
||||
}
|
||||
|
||||
private func eventItemViewText() -> Text {
|
||||
if let member = chatItem.memberDisplayName {
|
||||
if !revealed, let t = mergedGroupEventText {
|
||||
return chatEventText(t + Text(" ") + chatItem.timestampText)
|
||||
} else if let member = chatItem.memberDisplayName {
|
||||
return Text(member + " ")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
@@ -131,36 +144,44 @@ struct ChatItemContentView<Content: View>: View {
|
||||
}
|
||||
|
||||
private func chatFeatureView(_ feature: Feature, _ iconColor: Color) -> some View {
|
||||
CIChatFeatureView(chatItem: chatItem, feature: feature, iconColor: iconColor)
|
||||
CIChatFeatureView(chatItem: chatItem, revealed: $revealed, feature: feature, iconColor: iconColor)
|
||||
}
|
||||
|
||||
private var membersConnectedItemText: Text {
|
||||
if let t = membersConnectedText {
|
||||
return chatEventText(t, chatItem.timestampText)
|
||||
private var mergedGroupEventText: Text? {
|
||||
let (count, ns) = chatModel.getConnectedMemberNames(chatItem)
|
||||
let members: LocalizedStringKey =
|
||||
switch ns.count {
|
||||
case 1: "\(ns[0]) connected"
|
||||
case 2: "\(ns[0]) and \(ns[1]) connected"
|
||||
case 3: "\(ns[0] + ", " + ns[1]) and \(ns[2]) connected"
|
||||
default:
|
||||
ns.count > 3
|
||||
? "\(ns[0]), \(ns[1]) and \(ns.count - 2) other members connected"
|
||||
: ""
|
||||
}
|
||||
return if count <= 1 {
|
||||
nil
|
||||
} else if ns.count == 0 {
|
||||
Text("\(count) group events")
|
||||
} else if count > ns.count {
|
||||
Text(members) + Text(" ") + Text("and \(count - ns.count) other events")
|
||||
} else {
|
||||
return eventItemViewText()
|
||||
Text(members)
|
||||
}
|
||||
}
|
||||
|
||||
private var membersConnectedText: LocalizedStringKey? {
|
||||
let ns = chatModel.getConnectedMemberNames(chatItem)
|
||||
return ns.count > 3
|
||||
? "\(ns[0]), \(ns[1]) and \(ns.count - 2) other members connected"
|
||||
: ns.count == 3
|
||||
? "\(ns[0] + ", " + ns[1]) and \(ns[2]) connected"
|
||||
: ns.count == 2
|
||||
? "\(ns[0]) and \(ns[1]) connected"
|
||||
: nil
|
||||
}
|
||||
}
|
||||
|
||||
func chatEventText(_ eventText: LocalizedStringKey, _ ts: Text) -> Text {
|
||||
(Text(eventText) + Text(" ") + ts)
|
||||
func chatEventText(_ text: Text) -> Text {
|
||||
text
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.fontWeight(.light)
|
||||
}
|
||||
|
||||
func chatEventText(_ eventText: LocalizedStringKey, _ ts: Text) -> Text {
|
||||
chatEventText(Text(eventText) + Text(" ") + ts)
|
||||
}
|
||||
|
||||
func chatEventText(_ ci: ChatItem) -> Text {
|
||||
chatEventText("\(ci.content.text)", ci.timestampText)
|
||||
}
|
||||
@@ -168,15 +189,15 @@ func chatEventText(_ ci: ChatItem) -> Text {
|
||||
struct ChatItemView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group{
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too"), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂"), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂"), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂🙂"), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getDeletedContentSample(), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂", .sndSent(sndProgress: .complete), itemLive: true), revealed: Binding.constant(true))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemLive: true), revealed: Binding.constant(true))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), revealed: Binding.constant(false))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too"), revealed: Binding.constant(false))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂"), revealed: Binding.constant(false))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂"), revealed: Binding.constant(false))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂🙂"), revealed: Binding.constant(false))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getDeletedContentSample(), revealed: Binding.constant(false))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(false))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂", .sndSent(sndProgress: .complete), itemLive: true), revealed: Binding.constant(true))
|
||||
ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemLive: true), revealed: Binding.constant(true))
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 70))
|
||||
.environmentObject(Chat.sampleData)
|
||||
@@ -188,7 +209,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
|
||||
let ciFeatureContent = CIContent.rcvChatFeature(feature: .fullDelete, enabled: FeatureEnabled(forUser: false, forContact: false), param: nil)
|
||||
Group{
|
||||
ChatItemView(
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
chat: Chat.sampleData,
|
||||
chatItem: ChatItem(
|
||||
chatDir: .directRcv,
|
||||
meta: CIMeta.getSample(1, .now, "1 skipped message", .rcvRead, itemDeleted: .deleted(deletedTs: .now)),
|
||||
@@ -199,7 +220,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
|
||||
revealed: Binding.constant(true)
|
||||
)
|
||||
ChatItemView(
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
chat: Chat.sampleData,
|
||||
chatItem: ChatItem(
|
||||
chatDir: .directRcv,
|
||||
meta: CIMeta.getSample(1, .now, "1 skipped message", .rcvRead),
|
||||
@@ -210,7 +231,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
|
||||
revealed: Binding.constant(true)
|
||||
)
|
||||
ChatItemView(
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
chat: Chat.sampleData,
|
||||
chatItem: ChatItem(
|
||||
chatDir: .directRcv,
|
||||
meta: CIMeta.getSample(1, .now, "received invitation to join group team as admin", .rcvRead, itemDeleted: .deleted(deletedTs: .now)),
|
||||
@@ -221,7 +242,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
|
||||
revealed: Binding.constant(true)
|
||||
)
|
||||
ChatItemView(
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
chat: Chat.sampleData,
|
||||
chatItem: ChatItem(
|
||||
chatDir: .directRcv,
|
||||
meta: CIMeta.getSample(1, .now, "group event text", .rcvRead, itemDeleted: .deleted(deletedTs: .now)),
|
||||
@@ -232,7 +253,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
|
||||
revealed: Binding.constant(true)
|
||||
)
|
||||
ChatItemView(
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
chat: Chat.sampleData,
|
||||
chatItem: ChatItem(
|
||||
chatDir: .directRcv,
|
||||
meta: CIMeta.getSample(1, .now, ciFeatureContent.text, .rcvRead, itemDeleted: .deleted(deletedTs: .now)),
|
||||
|
||||
@@ -21,9 +21,7 @@ struct ChatView: View {
|
||||
@State private var showChatInfoSheet: Bool = false
|
||||
@State private var showAddMembersSheet: Bool = false
|
||||
@State private var composeState = ComposeState()
|
||||
@State private var deletingItem: ChatItem? = nil
|
||||
@State private var keyboardVisible = false
|
||||
@State private var showDeleteMessage = false
|
||||
@State private var connectionStats: ConnectionStats?
|
||||
@State private var customUserProfile: Profile?
|
||||
@State private var connectionCode: String?
|
||||
@@ -36,7 +34,12 @@ struct ChatView: View {
|
||||
@State private var searchText: String = ""
|
||||
@FocusState private var searchFocussed
|
||||
// opening GroupMemberInfoView on member icon
|
||||
@State private var selectedMember: GroupMember? = nil
|
||||
@State private var membersLoaded = false
|
||||
@State private var selectedMember: GMember? = nil
|
||||
// opening GroupLinkView on link button (incognito)
|
||||
@State private var showGroupLinkSheet: Bool = false
|
||||
@State private var groupLink: String?
|
||||
@State private var groupLinkMemberRole: GroupMemberRole = .member
|
||||
|
||||
var body: some View {
|
||||
if #available(iOS 16.0, *) {
|
||||
@@ -93,6 +96,8 @@ struct ChatView: View {
|
||||
if chatModel.chatId == nil {
|
||||
chatModel.chatItemStatuses = [:]
|
||||
chatModel.reversedChatItems = []
|
||||
chatModel.groupMembers = []
|
||||
membersLoaded = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,7 +114,7 @@ struct ChatView: View {
|
||||
connectionStats = stats
|
||||
customUserProfile = profile
|
||||
connectionCode = code
|
||||
if contact.activeConn.connectionCode != ct.activeConn.connectionCode {
|
||||
if contact.activeConn?.connectionCode != ct.activeConn?.connectionCode {
|
||||
chat.chatInfo = .direct(contact: ct)
|
||||
}
|
||||
}
|
||||
@@ -130,18 +135,21 @@ struct ChatView: View {
|
||||
}
|
||||
} else if case let .group(groupInfo) = cInfo {
|
||||
Button {
|
||||
Task {
|
||||
let groupMembers = await apiListMembers(groupInfo.groupId)
|
||||
await MainActor.run {
|
||||
ChatModel.shared.groupMembers = groupMembers
|
||||
showChatInfoSheet = true
|
||||
}
|
||||
}
|
||||
Task { await loadGroupMembers(groupInfo) { showChatInfoSheet = true } }
|
||||
} label: {
|
||||
ChatInfoToolbar(chat: chat)
|
||||
}
|
||||
.appSheet(isPresented: $showChatInfoSheet) {
|
||||
GroupChatInfoView(chat: chat, groupInfo: groupInfo)
|
||||
GroupChatInfoView(
|
||||
chat: chat,
|
||||
groupInfo: Binding(
|
||||
get: { groupInfo },
|
||||
set: { gInfo in
|
||||
chat.chatInfo = .group(groupInfo: gInfo)
|
||||
chat.created = Date.now
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,9 +181,16 @@ struct ChatView: View {
|
||||
HStack {
|
||||
if groupInfo.canAddMembers {
|
||||
if (chat.chatInfo.incognito) {
|
||||
Image(systemName: "person.crop.circle.badge.plus")
|
||||
.foregroundColor(Color(uiColor: .tertiaryLabel))
|
||||
.onTapGesture { AlertManager.shared.showAlert(cantInviteIncognitoAlert()) }
|
||||
groupLinkButton()
|
||||
.appSheet(isPresented: $showGroupLinkSheet) {
|
||||
GroupLinkView(
|
||||
groupId: groupInfo.groupId,
|
||||
groupLink: $groupLink,
|
||||
groupLinkMemberRole: $groupLinkMemberRole,
|
||||
showTitle: true,
|
||||
creatingGroup: false
|
||||
)
|
||||
}
|
||||
} else {
|
||||
addMembersButton()
|
||||
.appSheet(isPresented: $showAddMembersSheet) {
|
||||
@@ -197,6 +212,17 @@ struct ChatView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func loadGroupMembers(_ groupInfo: GroupInfo, updateView: @escaping () -> Void = {}) async {
|
||||
let groupMembers = await apiListMembers(groupInfo.groupId)
|
||||
await MainActor.run {
|
||||
if chatModel.chatId == groupInfo.id {
|
||||
chatModel.groupMembers = groupMembers.map { GMember.init($0) }
|
||||
membersLoaded = true
|
||||
updateView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func initChatView() {
|
||||
let cInfo = chat.chatInfo
|
||||
if case let .direct(contact) = cInfo {
|
||||
@@ -405,19 +431,32 @@ struct ChatView: View {
|
||||
private func addMembersButton() -> some View {
|
||||
Button {
|
||||
if case let .group(gInfo) = chat.chatInfo {
|
||||
Task {
|
||||
let groupMembers = await apiListMembers(gInfo.groupId)
|
||||
await MainActor.run {
|
||||
ChatModel.shared.groupMembers = groupMembers
|
||||
showAddMembersSheet = true
|
||||
}
|
||||
}
|
||||
Task { await loadGroupMembers(gInfo) { showAddMembersSheet = true } }
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "person.crop.circle.badge.plus")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func groupLinkButton() -> some View {
|
||||
Button {
|
||||
if case let .group(gInfo) = chat.chatInfo {
|
||||
Task {
|
||||
do {
|
||||
if let link = try apiGetGroupLink(gInfo.groupId) {
|
||||
(groupLink, groupLinkMemberRole) = link
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("ChatView apiGetGroupLink: \(responseError(error))")
|
||||
}
|
||||
showGroupLinkSheet = true
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "link.badge.plus")
|
||||
}
|
||||
}
|
||||
|
||||
private func loadChatItems(_ cInfo: ChatInfo, _ ci: ChatItem, _ proxy: ScrollViewProxy) {
|
||||
if let firstItem = chatModel.reversedChatItems.last, firstItem.id == ci.id {
|
||||
if loadingItems || firstPage { return }
|
||||
@@ -447,73 +486,30 @@ struct ChatView: View {
|
||||
}
|
||||
|
||||
@ViewBuilder private func chatItemView(_ ci: ChatItem, _ maxWidth: CGFloat) -> some View {
|
||||
if case let .groupRcv(member) = ci.chatDir,
|
||||
case let .group(groupInfo) = chat.chatInfo {
|
||||
let (prevItem, nextItem) = chatModel.getChatItemNeighbors(ci)
|
||||
if ci.memberConnected != nil && nextItem?.memberConnected != nil {
|
||||
// memberConnected events are aggregated at the last chat item in a row of such events, see ChatItemView
|
||||
ZStack {} // scroll doesn't work if it's EmptyView()
|
||||
} else {
|
||||
if prevItem == nil || showMemberImage(member, prevItem) {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
if ci.content.showMemberName {
|
||||
Text(member.displayName)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.padding(.leading, memberImageSize + 14)
|
||||
.padding(.top, 7)
|
||||
}
|
||||
HStack(alignment: .top, spacing: 8) {
|
||||
ProfileImage(imageStr: member.memberProfile.image)
|
||||
.frame(width: memberImageSize, height: memberImageSize)
|
||||
.onTapGesture { selectedMember = member }
|
||||
.appSheet(item: $selectedMember) { member in
|
||||
GroupMemberInfoView(groupInfo: groupInfo, member: member, navigation: true)
|
||||
}
|
||||
chatItemWithMenu(ci, maxWidth)
|
||||
}
|
||||
}
|
||||
.padding(.top, 5)
|
||||
.padding(.trailing)
|
||||
.padding(.leading, 12)
|
||||
} else {
|
||||
chatItemWithMenu(ci, maxWidth)
|
||||
.padding(.top, 5)
|
||||
.padding(.trailing)
|
||||
.padding(.leading, memberImageSize + 8 + 12)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
chatItemWithMenu(ci, maxWidth)
|
||||
.padding(.horizontal)
|
||||
.padding(.top, 5)
|
||||
}
|
||||
}
|
||||
|
||||
private func chatItemWithMenu(_ ci: ChatItem, _ maxWidth: CGFloat) -> some View {
|
||||
ChatItemWithMenu(
|
||||
ci: ci,
|
||||
chat: chat,
|
||||
chatItem: ci,
|
||||
maxWidth: maxWidth,
|
||||
scrollProxy: scrollProxy,
|
||||
deleteMessage: deleteMessage,
|
||||
deletingItem: $deletingItem,
|
||||
composeState: $composeState,
|
||||
showDeleteMessage: $showDeleteMessage
|
||||
selectedMember: $selectedMember,
|
||||
chatView: self
|
||||
)
|
||||
.environmentObject(chat)
|
||||
}
|
||||
|
||||
private struct ChatItemWithMenu: View {
|
||||
@EnvironmentObject var chat: Chat
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
var ci: ChatItem
|
||||
@ObservedObject var chat: Chat
|
||||
var chatItem: ChatItem
|
||||
var maxWidth: CGFloat
|
||||
var scrollProxy: ScrollViewProxy?
|
||||
var deleteMessage: (CIDeleteMode) -> Void
|
||||
@Binding var deletingItem: ChatItem?
|
||||
@Binding var composeState: ComposeState
|
||||
@Binding var showDeleteMessage: Bool
|
||||
@Binding var selectedMember: GMember?
|
||||
var chatView: ChatView
|
||||
|
||||
@State private var deletingItem: ChatItem? = nil
|
||||
@State private var showDeleteMessage = false
|
||||
@State private var deletingItems: [Int64] = []
|
||||
@State private var showDeleteMessages = false
|
||||
@State private var revealed = false
|
||||
@State private var showChatItemInfoSheet: Bool = false
|
||||
@State private var chatItemInfo: ChatItemInfo?
|
||||
@@ -525,18 +521,114 @@ struct ChatView: View {
|
||||
@State private var playbackTime: TimeInterval?
|
||||
|
||||
var body: some View {
|
||||
let (currIndex, nextItem) = m.getNextChatItem(chatItem)
|
||||
let ciCategory = chatItem.mergeCategory
|
||||
if (ciCategory != nil && ciCategory == nextItem?.mergeCategory) {
|
||||
// memberConnected events and deleted items are aggregated at the last chat item in a row, see ChatItemView
|
||||
ZStack {} // scroll doesn't work if it's EmptyView()
|
||||
} else {
|
||||
let (prevHidden, prevItem) = m.getPrevShownChatItem(currIndex, ciCategory)
|
||||
let range = itemsRange(currIndex, prevHidden)
|
||||
if revealed, let range = range {
|
||||
let items = Array(zip(Array(range), m.reversedChatItems[range]))
|
||||
ForEach(items, id: \.1.viewId) { (i, ci) in
|
||||
let prev = i == prevHidden ? prevItem : m.reversedChatItems[i + 1]
|
||||
chatItemView(ci, nil, prev)
|
||||
}
|
||||
} else {
|
||||
chatItemView(chatItem, range, prevItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder func chatItemView(_ ci: ChatItem, _ range: ClosedRange<Int>?, _ prevItem: ChatItem?) -> some View {
|
||||
if case let .groupRcv(member) = ci.chatDir,
|
||||
case let .group(groupInfo) = chat.chatInfo {
|
||||
let (prevMember, memCount): (GroupMember?, Int) =
|
||||
if let range = range {
|
||||
m.getPrevHiddenMember(member, range)
|
||||
} else {
|
||||
(nil, 1)
|
||||
}
|
||||
if prevItem == nil || showMemberImage(member, prevItem) || prevMember != nil {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
if ci.content.showMemberName {
|
||||
Text(memberNames(member, prevMember, memCount))
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.padding(.leading, memberImageSize + 14)
|
||||
.padding(.top, 7)
|
||||
}
|
||||
HStack(alignment: .top, spacing: 8) {
|
||||
ProfileImage(imageStr: member.memberProfile.image)
|
||||
.frame(width: memberImageSize, height: memberImageSize)
|
||||
.onTapGesture {
|
||||
if chatView.membersLoaded {
|
||||
selectedMember = m.getGroupMember(member.groupMemberId)
|
||||
} else {
|
||||
Task {
|
||||
await chatView.loadGroupMembers(groupInfo) {
|
||||
selectedMember = m.getGroupMember(member.groupMemberId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.appSheet(item: $selectedMember) { member in
|
||||
GroupMemberInfoView(groupInfo: groupInfo, groupMember: member, navigation: true)
|
||||
}
|
||||
chatItemWithMenu(ci, range, maxWidth)
|
||||
}
|
||||
}
|
||||
.padding(.top, 5)
|
||||
.padding(.trailing)
|
||||
.padding(.leading, 12)
|
||||
} else {
|
||||
chatItemWithMenu(ci, range, maxWidth)
|
||||
.padding(.top, 5)
|
||||
.padding(.trailing)
|
||||
.padding(.leading, memberImageSize + 8 + 12)
|
||||
}
|
||||
} else {
|
||||
chatItemWithMenu(ci, range, maxWidth)
|
||||
.padding(.horizontal)
|
||||
.padding(.top, 5)
|
||||
}
|
||||
}
|
||||
|
||||
private func memberNames(_ member: GroupMember, _ prevMember: GroupMember?, _ memCount: Int) -> LocalizedStringKey {
|
||||
let name = member.displayName
|
||||
return if let prevName = prevMember?.displayName {
|
||||
memCount > 2
|
||||
? "\(name), \(prevName) and \(memCount - 2) members"
|
||||
: "\(name) and \(prevName)"
|
||||
} else {
|
||||
"\(name)"
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder func chatItemWithMenu(_ ci: ChatItem, _ range: ClosedRange<Int>?, _ maxWidth: CGFloat) -> some View {
|
||||
let alignment: Alignment = ci.chatDir.sent ? .trailing : .leading
|
||||
let uiMenu: Binding<UIMenu> = Binding(
|
||||
get: { UIMenu(title: "", children: menu(live: composeState.liveMessage != nil)) },
|
||||
get: { UIMenu(title: "", children: menu(ci, range, live: composeState.liveMessage != nil)) },
|
||||
set: { _ in }
|
||||
)
|
||||
|
||||
VStack(alignment: alignment.horizontal, spacing: 3) {
|
||||
ChatItemView(chatInfo: chat.chatInfo, chatItem: ci, maxWidth: maxWidth, scrollProxy: scrollProxy, revealed: $revealed, allowMenu: $allowMenu, audioPlayer: $audioPlayer, playbackState: $playbackState, playbackTime: $playbackTime)
|
||||
.uiKitContextMenu(menu: uiMenu, allowMenu: $allowMenu)
|
||||
.accessibilityLabel("")
|
||||
ChatItemView(
|
||||
chat: chat,
|
||||
chatItem: ci,
|
||||
maxWidth: maxWidth,
|
||||
scrollProxy: chatView.scrollProxy,
|
||||
revealed: $revealed,
|
||||
allowMenu: $allowMenu,
|
||||
audioPlayer: $audioPlayer,
|
||||
playbackState: $playbackState,
|
||||
playbackTime: $playbackTime
|
||||
)
|
||||
.uiKitContextMenu(menu: uiMenu, allowMenu: $allowMenu)
|
||||
.accessibilityLabel("")
|
||||
if ci.content.msgContent != nil && (ci.meta.itemDeleted == nil || revealed) && ci.reactions.count > 0 {
|
||||
chatItemReactions()
|
||||
chatItemReactions(ci)
|
||||
.padding(.bottom, 4)
|
||||
}
|
||||
}
|
||||
@@ -550,6 +642,11 @@ struct ChatView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.confirmationDialog(deleteMessagesTitle, isPresented: $showDeleteMessages, titleVisibility: .visible) {
|
||||
Button("Delete for me", role: .destructive) {
|
||||
deleteMessages()
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: maxWidth, maxHeight: .infinity, alignment: alignment)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, alignment: alignment)
|
||||
.onDisappear {
|
||||
@@ -567,7 +664,15 @@ struct ChatView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func chatItemReactions() -> some View {
|
||||
private func showMemberImage(_ member: GroupMember, _ prevItem: ChatItem?) -> Bool {
|
||||
switch (prevItem?.chatDir) {
|
||||
case .groupSnd: return true
|
||||
case let .groupRcv(prevMember): return prevMember.groupMemberId != member.groupMemberId
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
private func chatItemReactions(_ ci: ChatItem) -> some View {
|
||||
HStack(spacing: 4) {
|
||||
ForEach(ci.reactions, id: \.reaction) { r in
|
||||
let v = HStack(spacing: 4) {
|
||||
@@ -587,7 +692,7 @@ struct ChatView: View {
|
||||
|
||||
if chat.chatInfo.featureEnabled(.reactions) && (ci.allowAddReaction || r.userReacted) {
|
||||
v.onTapGesture {
|
||||
setReaction(add: !r.userReacted, reaction: r.reaction)
|
||||
setReaction(ci, add: !r.userReacted, reaction: r.reaction)
|
||||
}
|
||||
} else {
|
||||
v
|
||||
@@ -596,10 +701,10 @@ struct ChatView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func menu(live: Bool) -> [UIMenuElement] {
|
||||
private func menu(_ ci: ChatItem, _ range: ClosedRange<Int>?, live: Bool) -> [UIMenuElement] {
|
||||
var menu: [UIMenuElement] = []
|
||||
if let mc = ci.content.msgContent, ci.meta.itemDeleted == nil || revealed {
|
||||
let rs = allReactions()
|
||||
let rs = allReactions(ci)
|
||||
if chat.chatInfo.featureEnabled(.reactions) && ci.allowAddReaction,
|
||||
rs.count > 0 {
|
||||
var rm: UIMenu
|
||||
@@ -616,10 +721,10 @@ struct ChatView: View {
|
||||
menu.append(rm)
|
||||
}
|
||||
if ci.meta.itemDeleted == nil && !ci.isLiveDummy && !live {
|
||||
menu.append(replyUIAction())
|
||||
menu.append(replyUIAction(ci))
|
||||
}
|
||||
menu.append(shareUIAction())
|
||||
menu.append(copyUIAction())
|
||||
menu.append(shareUIAction(ci))
|
||||
menu.append(copyUIAction(ci))
|
||||
if let fileSource = getLoadedFileSource(ci.file) {
|
||||
if case .image = ci.content.msgContent, let image = getLoadedImage(ci.file) {
|
||||
if image.imageData != nil {
|
||||
@@ -632,9 +737,9 @@ struct ChatView: View {
|
||||
}
|
||||
}
|
||||
if ci.meta.editable && !mc.isVoice && !live {
|
||||
menu.append(editAction())
|
||||
menu.append(editAction(ci))
|
||||
}
|
||||
menu.append(viewInfoUIAction())
|
||||
menu.append(viewInfoUIAction(ci))
|
||||
if revealed {
|
||||
menu.append(hideUIAction())
|
||||
}
|
||||
@@ -644,25 +749,31 @@ struct ChatView: View {
|
||||
menu.append(cancelFileUIAction(file.fileId, cancelAction))
|
||||
}
|
||||
if !live || !ci.meta.isLive {
|
||||
menu.append(deleteUIAction())
|
||||
menu.append(deleteUIAction(ci))
|
||||
}
|
||||
if let (groupInfo, _) = ci.memberToModerate(chat.chatInfo) {
|
||||
menu.append(moderateUIAction(groupInfo))
|
||||
menu.append(moderateUIAction(ci, groupInfo))
|
||||
}
|
||||
} else if ci.meta.itemDeleted != nil {
|
||||
if !ci.isDeletedContent {
|
||||
if revealed {
|
||||
menu.append(hideUIAction())
|
||||
} else if !ci.isDeletedContent {
|
||||
menu.append(revealUIAction())
|
||||
} else if range != nil {
|
||||
menu.append(expandUIAction())
|
||||
}
|
||||
menu.append(viewInfoUIAction())
|
||||
menu.append(deleteUIAction())
|
||||
menu.append(viewInfoUIAction(ci))
|
||||
menu.append(deleteUIAction(ci))
|
||||
} else if ci.isDeletedContent {
|
||||
menu.append(viewInfoUIAction())
|
||||
menu.append(deleteUIAction())
|
||||
menu.append(viewInfoUIAction(ci))
|
||||
menu.append(deleteUIAction(ci))
|
||||
} else if ci.mergeCategory != nil && ((range?.count ?? 0) > 1 || revealed) {
|
||||
menu.append(revealed ? shrinkUIAction() : expandUIAction())
|
||||
}
|
||||
return menu
|
||||
}
|
||||
|
||||
private func replyUIAction() -> UIAction {
|
||||
private func replyUIAction(_ ci: ChatItem) -> UIAction {
|
||||
UIAction(
|
||||
title: NSLocalizedString("Reply", comment: "chat item action"),
|
||||
image: UIImage(systemName: "arrowshape.turn.up.left")
|
||||
@@ -697,11 +808,11 @@ struct ChatView: View {
|
||||
)
|
||||
}
|
||||
|
||||
private func allReactions() -> [UIAction] {
|
||||
private func allReactions(_ ci: ChatItem) -> [UIAction] {
|
||||
MsgReaction.values.compactMap { r in
|
||||
ci.reactions.contains(where: { $0.userReacted && $0.reaction == r })
|
||||
? nil
|
||||
: UIAction(title: r.text) { _ in setReaction(add: true, reaction: r) }
|
||||
: UIAction(title: r.text) { _ in setReaction(ci, add: true, reaction: r) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -709,7 +820,7 @@ struct ChatView: View {
|
||||
rs.count > 4 ? 3 : 4
|
||||
}
|
||||
|
||||
private func setReaction(add: Bool, reaction: MsgReaction) {
|
||||
private func setReaction(_ ci: ChatItem, add: Bool, reaction: MsgReaction) {
|
||||
Task {
|
||||
do {
|
||||
let cInfo = chat.chatInfo
|
||||
@@ -721,7 +832,7 @@ struct ChatView: View {
|
||||
reaction: reaction
|
||||
)
|
||||
await MainActor.run {
|
||||
ChatModel.shared.updateChatItem(chat.chatInfo, chatItem)
|
||||
m.updateChatItem(chat.chatInfo, chatItem)
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("apiChatItemReaction error: \(responseError(error))")
|
||||
@@ -729,7 +840,7 @@ struct ChatView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func shareUIAction() -> UIAction {
|
||||
private func shareUIAction(_ ci: ChatItem) -> UIAction {
|
||||
UIAction(
|
||||
title: NSLocalizedString("Share", comment: "chat item action"),
|
||||
image: UIImage(systemName: "square.and.arrow.up")
|
||||
@@ -742,7 +853,7 @@ struct ChatView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func copyUIAction() -> UIAction {
|
||||
private func copyUIAction(_ ci: ChatItem) -> UIAction {
|
||||
UIAction(
|
||||
title: NSLocalizedString("Copy", comment: "chat item action"),
|
||||
image: UIImage(systemName: "doc.on.doc")
|
||||
@@ -775,7 +886,7 @@ struct ChatView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func editAction() -> UIAction {
|
||||
private func editAction(_ ci: ChatItem) -> UIAction {
|
||||
UIAction(
|
||||
title: NSLocalizedString("Edit", comment: "chat item action"),
|
||||
image: UIImage(systemName: "square.and.pencil")
|
||||
@@ -786,7 +897,7 @@ struct ChatView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func viewInfoUIAction() -> UIAction {
|
||||
private func viewInfoUIAction(_ ci: ChatItem) -> UIAction {
|
||||
UIAction(
|
||||
title: NSLocalizedString("Info", comment: "chat item action"),
|
||||
image: UIImage(systemName: "info.circle")
|
||||
@@ -799,10 +910,7 @@ struct ChatView: View {
|
||||
chatItemInfo = ciInfo
|
||||
}
|
||||
if case let .group(gInfo) = chat.chatInfo {
|
||||
let groupMembers = await apiListMembers(gInfo.groupId)
|
||||
await MainActor.run {
|
||||
ChatModel.shared.groupMembers = groupMembers
|
||||
}
|
||||
await chatView.loadGroupMembers(gInfo)
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("apiGetChatItemInfo error: \(responseError(error))")
|
||||
@@ -823,7 +931,7 @@ struct ChatView: View {
|
||||
message: Text(cancelAction.alert.message),
|
||||
primaryButton: .destructive(Text(cancelAction.alert.confirm)) {
|
||||
Task {
|
||||
if let user = ChatModel.shared.currentUser {
|
||||
if let user = m.currentUser {
|
||||
await cancelFile(user: user, fileId: fileId)
|
||||
}
|
||||
}
|
||||
@@ -844,18 +952,45 @@ struct ChatView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func deleteUIAction() -> UIAction {
|
||||
private func deleteUIAction(_ ci: ChatItem) -> UIAction {
|
||||
UIAction(
|
||||
title: NSLocalizedString("Delete", comment: "chat item action"),
|
||||
image: UIImage(systemName: "trash"),
|
||||
attributes: [.destructive]
|
||||
) { _ in
|
||||
showDeleteMessage = true
|
||||
deletingItem = ci
|
||||
if !revealed && ci.meta.itemDeleted != nil,
|
||||
let currIndex = m.getChatItemIndex(ci),
|
||||
let ciCategory = ci.mergeCategory {
|
||||
let (prevHidden, _) = m.getPrevShownChatItem(currIndex, ciCategory)
|
||||
if let range = itemsRange(currIndex, prevHidden) {
|
||||
var itemIds: [Int64] = []
|
||||
for i in range {
|
||||
itemIds.append(m.reversedChatItems[i].id)
|
||||
}
|
||||
showDeleteMessages = true
|
||||
deletingItems = itemIds
|
||||
} else {
|
||||
showDeleteMessage = true
|
||||
deletingItem = ci
|
||||
}
|
||||
} else {
|
||||
showDeleteMessage = true
|
||||
deletingItem = ci
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func moderateUIAction(_ groupInfo: GroupInfo) -> UIAction {
|
||||
private func itemsRange(_ currIndex: Int?, _ prevHidden: Int?) -> ClosedRange<Int>? {
|
||||
if let currIndex = currIndex,
|
||||
let prevHidden = prevHidden,
|
||||
prevHidden > currIndex {
|
||||
currIndex...prevHidden
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
private func moderateUIAction(_ ci: ChatItem, _ groupInfo: GroupInfo) -> UIAction {
|
||||
UIAction(
|
||||
title: NSLocalizedString("Moderate", comment: "chat item action"),
|
||||
image: UIImage(systemName: "flag"),
|
||||
@@ -887,20 +1022,105 @@ struct ChatView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func expandUIAction() -> UIAction {
|
||||
UIAction(
|
||||
title: NSLocalizedString("Expand", comment: "chat item action"),
|
||||
image: UIImage(systemName: "arrow.up.and.line.horizontal.and.arrow.down")
|
||||
) { _ in
|
||||
withAnimation {
|
||||
revealed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func shrinkUIAction() -> UIAction {
|
||||
UIAction(
|
||||
title: NSLocalizedString("Hide", comment: "chat item action"),
|
||||
image: UIImage(systemName: "arrow.down.and.line.horizontal.and.arrow.up")
|
||||
) { _ in
|
||||
withAnimation {
|
||||
revealed = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var broadcastDeleteButtonText: LocalizedStringKey {
|
||||
chat.chatInfo.featureEnabled(.fullDelete) ? "Delete for everyone" : "Mark deleted for everyone"
|
||||
}
|
||||
}
|
||||
|
||||
private func showMemberImage(_ member: GroupMember, _ prevItem: ChatItem?) -> Bool {
|
||||
switch (prevItem?.chatDir) {
|
||||
case .groupSnd: return true
|
||||
case let .groupRcv(prevMember): return prevMember.groupMemberId != member.groupMemberId
|
||||
default: return false
|
||||
var deleteMessagesTitle: LocalizedStringKey {
|
||||
let n = deletingItems.count
|
||||
return n == 1 ? "Delete message?" : "Delete \(n) messages?"
|
||||
}
|
||||
|
||||
private func deleteMessages() {
|
||||
let itemIds = deletingItems
|
||||
if itemIds.count > 0 {
|
||||
let chatInfo = chat.chatInfo
|
||||
Task {
|
||||
var deletedItems: [ChatItem] = []
|
||||
for itemId in itemIds {
|
||||
do {
|
||||
let (di, _) = try await apiDeleteChatItem(
|
||||
type: chatInfo.chatType,
|
||||
id: chatInfo.apiId,
|
||||
itemId: itemId,
|
||||
mode: .cidmInternal
|
||||
)
|
||||
deletedItems.append(di)
|
||||
} catch {
|
||||
logger.error("ChatView.deleteMessage error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
await MainActor.run {
|
||||
for di in deletedItems {
|
||||
m.removeChatItem(chatInfo, di)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func deleteMessage(_ mode: CIDeleteMode) {
|
||||
logger.debug("ChatView deleteMessage")
|
||||
Task {
|
||||
logger.debug("ChatView deleteMessage: in Task")
|
||||
do {
|
||||
if let di = deletingItem {
|
||||
var deletedItem: ChatItem
|
||||
var toItem: ChatItem?
|
||||
if case .cidmBroadcast = mode,
|
||||
let (groupInfo, groupMember) = di.memberToModerate(chat.chatInfo) {
|
||||
(deletedItem, toItem) = try await apiDeleteMemberChatItem(
|
||||
groupId: groupInfo.apiId,
|
||||
groupMemberId: groupMember.groupMemberId,
|
||||
itemId: di.id
|
||||
)
|
||||
} else {
|
||||
(deletedItem, toItem) = try await apiDeleteChatItem(
|
||||
type: chat.chatInfo.chatType,
|
||||
id: chat.chatInfo.apiId,
|
||||
itemId: di.id,
|
||||
mode: mode
|
||||
)
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
deletingItem = nil
|
||||
if let toItem = toItem {
|
||||
_ = m.upsertChatItem(chat.chatInfo, toItem)
|
||||
} else {
|
||||
m.removeChatItem(chat.chatInfo, deletedItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
logger.error("ChatView.deleteMessage error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func scrollToBottom(_ proxy: ScrollViewProxy) {
|
||||
if let ci = chatModel.reversedChatItems.first {
|
||||
withAnimation { proxy.scrollTo(ci.viewId, anchor: .top) }
|
||||
@@ -912,44 +1132,6 @@ struct ChatView: View {
|
||||
withAnimation { proxy.scrollTo(ci.viewId, anchor: .top) }
|
||||
}
|
||||
}
|
||||
|
||||
private func deleteMessage(_ mode: CIDeleteMode) {
|
||||
logger.debug("ChatView deleteMessage")
|
||||
Task {
|
||||
logger.debug("ChatView deleteMessage: in Task")
|
||||
do {
|
||||
if let di = deletingItem {
|
||||
var deletedItem: ChatItem
|
||||
var toItem: ChatItem?
|
||||
if case .cidmBroadcast = mode,
|
||||
let (groupInfo, groupMember) = di.memberToModerate(chat.chatInfo) {
|
||||
(deletedItem, toItem) = try await apiDeleteMemberChatItem(
|
||||
groupId: groupInfo.apiId,
|
||||
groupMemberId: groupMember.groupMemberId,
|
||||
itemId: di.id
|
||||
)
|
||||
} else {
|
||||
(deletedItem, toItem) = try await apiDeleteChatItem(
|
||||
type: chat.chatInfo.chatType,
|
||||
id: chat.chatInfo.apiId,
|
||||
itemId: di.id,
|
||||
mode: mode
|
||||
)
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
deletingItem = nil
|
||||
if let toItem = toItem {
|
||||
_ = chatModel.upsertChatItem(chat.chatInfo, toItem)
|
||||
} else {
|
||||
chatModel.removeChatItem(chat.chatInfo, deletedItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
logger.error("ChatView.deleteMessage error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder func toggleNtfsButton(_ chat: Chat) -> some View {
|
||||
|
||||
@@ -592,12 +592,14 @@ struct ComposeView: View {
|
||||
EmptyView()
|
||||
case let .quotedItem(chatItem: quotedItem):
|
||||
ContextItemView(
|
||||
chat: chat,
|
||||
contextItem: quotedItem,
|
||||
contextIcon: "arrowshape.turn.up.left",
|
||||
cancelContextItem: { composeState = composeState.copy(contextItem: .noContextItem) }
|
||||
)
|
||||
case let .editingItem(chatItem: editingItem):
|
||||
ContextItemView(
|
||||
chat: chat,
|
||||
contextItem: editingItem,
|
||||
contextIcon: "pencil",
|
||||
cancelContextItem: { clearState() }
|
||||
|
||||
@@ -11,6 +11,7 @@ import SimpleXChat
|
||||
|
||||
struct ContextItemView: View {
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@ObservedObject var chat: Chat
|
||||
let contextItem: ChatItem
|
||||
let contextIcon: String
|
||||
let cancelContextItem: () -> Void
|
||||
@@ -48,6 +49,7 @@ struct ContextItemView: View {
|
||||
|
||||
private func msgContentView(lines: Int) -> some View {
|
||||
MsgContentView(
|
||||
chat: chat,
|
||||
text: contextItem.text,
|
||||
formattedText: contextItem.formattedText
|
||||
)
|
||||
@@ -59,6 +61,6 @@ struct ContextItemView: View {
|
||||
struct ContextItemView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let contextItem: ChatItem = ChatItem.getSample(1, .directSnd, .now, "hello")
|
||||
return ContextItemView(contextItem: contextItem, contextIcon: "pencil.circle", cancelContextItem: {})
|
||||
return ContextItemView(chat: Chat.sampleData, contextItem: contextItem, contextIcon: "pencil.circle", cancelContextItem: {})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,20 +14,28 @@ import PhotosUI
|
||||
struct NativeTextEditor: UIViewRepresentable {
|
||||
@Binding var text: String
|
||||
@Binding var disableEditing: Bool
|
||||
let height: CGFloat
|
||||
let font: UIFont
|
||||
@Binding var height: CGFloat
|
||||
@Binding var focused: Bool
|
||||
let alignment: TextAlignment
|
||||
let onImagesAdded: ([UploadContent]) -> Void
|
||||
|
||||
private let minHeight: CGFloat = 37
|
||||
|
||||
private let defaultHeight: CGFloat = {
|
||||
let field = CustomUITextField(height: Binding.constant(0))
|
||||
field.textContainerInset = UIEdgeInsets(top: 8, left: 5, bottom: 6, right: 4)
|
||||
return min(max(field.sizeThatFits(CGSizeMake(field.frame.size.width, CGFloat.greatestFiniteMagnitude)).height, 37), 360).rounded(.down)
|
||||
}()
|
||||
|
||||
func makeUIView(context: Context) -> UITextView {
|
||||
let field = CustomUITextField()
|
||||
let field = CustomUITextField(height: _height)
|
||||
field.text = text
|
||||
field.font = font
|
||||
field.textAlignment = alignment == .leading ? .left : .right
|
||||
field.autocapitalizationType = .sentences
|
||||
field.setOnTextChangedListener { newText, images in
|
||||
if !disableEditing {
|
||||
// Speed up the process of updating layout, reduce jumping content on screen
|
||||
if !isShortEmoji(newText) { updateHeight(field) }
|
||||
text = newText
|
||||
} else {
|
||||
field.text = text
|
||||
@@ -39,24 +47,72 @@ struct NativeTextEditor: UIViewRepresentable {
|
||||
field.setOnFocusChangedListener { focused = $0 }
|
||||
field.delegate = field
|
||||
field.textContainerInset = UIEdgeInsets(top: 8, left: 5, bottom: 6, right: 4)
|
||||
updateFont(field)
|
||||
updateHeight(field)
|
||||
return field
|
||||
}
|
||||
|
||||
func updateUIView(_ field: UITextView, context: Context) {
|
||||
field.text = text
|
||||
field.font = font
|
||||
field.textAlignment = alignment == .leading ? .left : .right
|
||||
updateFont(field)
|
||||
updateHeight(field)
|
||||
}
|
||||
|
||||
private func updateHeight(_ field: UITextView) {
|
||||
let maxHeight = min(360, field.font!.lineHeight * 12)
|
||||
// When having emoji in text view and then removing it, sizeThatFits shows previous size (too big for empty text view), so using work around with default size
|
||||
let newHeight = field.text == ""
|
||||
? defaultHeight
|
||||
: min(max(field.sizeThatFits(CGSizeMake(field.frame.size.width, CGFloat.greatestFiniteMagnitude)).height, minHeight), maxHeight).rounded(.down)
|
||||
|
||||
if field.frame.size.height != newHeight {
|
||||
field.frame.size = CGSizeMake(field.frame.size.width, newHeight)
|
||||
(field as! CustomUITextField).invalidateIntrinsicContentHeight(newHeight)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateFont(_ field: UITextView) {
|
||||
field.font = isShortEmoji(field.text)
|
||||
? (field.text.count < 4 ? largeEmojiUIFont : mediumEmojiUIFont)
|
||||
: UIFont.preferredFont(forTextStyle: .body)
|
||||
}
|
||||
}
|
||||
|
||||
private class CustomUITextField: UITextView, UITextViewDelegate {
|
||||
var height: Binding<CGFloat>
|
||||
var newHeight: CGFloat = 0
|
||||
var onTextChanged: (String, [UploadContent]) -> Void = { newText, image in }
|
||||
var onFocusChanged: (Bool) -> Void = { focused in }
|
||||
|
||||
init(height: Binding<CGFloat>) {
|
||||
self.height = height
|
||||
super.init(frame: .zero, textContainer: nil)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("Not implemented")
|
||||
}
|
||||
|
||||
// This func here needed because using frame.size.height in intrinsicContentSize while loading a screen with text (for example. when you have a draft),
|
||||
// produces incorrect height because at that point intrinsicContentSize has old value of frame.size.height even if it was set to new value right before the call
|
||||
// (who knows why...)
|
||||
func invalidateIntrinsicContentHeight(_ newHeight: CGFloat) {
|
||||
self.newHeight = newHeight
|
||||
invalidateIntrinsicContentSize()
|
||||
}
|
||||
|
||||
override var intrinsicContentSize: CGSize {
|
||||
if height.wrappedValue != newHeight {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now(), execute: { self.height.wrappedValue = self.newHeight })
|
||||
}
|
||||
return CGSizeMake(0, newHeight)
|
||||
}
|
||||
|
||||
func setOnTextChangedListener(onTextChanged: @escaping (String, [UploadContent]) -> Void) {
|
||||
self.onTextChanged = onTextChanged
|
||||
}
|
||||
|
||||
|
||||
func setOnFocusChangedListener(onFocusChanged: @escaping (Bool) -> Void) {
|
||||
self.onFocusChanged = onFocusChanged
|
||||
}
|
||||
@@ -144,14 +200,14 @@ private class CustomUITextField: UITextView, UITextViewDelegate {
|
||||
|
||||
struct NativeTextEditor_Previews: PreviewProvider{
|
||||
static var previews: some View {
|
||||
return NativeTextEditor(
|
||||
NativeTextEditor(
|
||||
text: Binding.constant("Hello, world!"),
|
||||
disableEditing: Binding.constant(false),
|
||||
height: 100,
|
||||
font: UIFont.preferredFont(forTextStyle: .body),
|
||||
height: Binding.constant(100),
|
||||
focused: Binding.constant(false),
|
||||
alignment: TextAlignment.leading,
|
||||
onImagesAdded: { _ in }
|
||||
)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,15 +32,12 @@ struct SendMessageView: View {
|
||||
var sendButtonColor = Color.accentColor
|
||||
@State private var teHeight: CGFloat = 42
|
||||
@State private var teFont: Font = .body
|
||||
@State private var teUiFont: UIFont = UIFont.preferredFont(forTextStyle: .body)
|
||||
@State private var sendButtonSize: CGFloat = 29
|
||||
@State private var sendButtonOpacity: CGFloat = 1
|
||||
@State private var showCustomDisappearingMessageDialogue = false
|
||||
@State private var showCustomTimePicker = false
|
||||
@State private var selectedDisappearingMessageTime: Int? = customDisappearingMessageTimeDefault.get()
|
||||
@State private var progressByTimeout = false
|
||||
var maxHeight: CGFloat = 360
|
||||
var minHeight: CGFloat = 37
|
||||
@AppStorage(DEFAULT_LIVE_MESSAGE_ALERT_SHOWN) private var liveMessageAlertShown = false
|
||||
|
||||
var body: some View {
|
||||
@@ -57,30 +54,16 @@ struct SendMessageView: View {
|
||||
.frame(maxWidth: .infinity)
|
||||
} else {
|
||||
let alignment: TextAlignment = isRightToLeft(composeState.message) ? .trailing : .leading
|
||||
Text(composeState.message)
|
||||
.lineLimit(10)
|
||||
.font(teFont)
|
||||
.multilineTextAlignment(alignment)
|
||||
// put text on top (after NativeTextEditor) and set color to precisely align it on changes
|
||||
// .foregroundColor(.red)
|
||||
.foregroundColor(.clear)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.top, 8)
|
||||
.padding(.bottom, 6)
|
||||
.matchedGeometryEffect(id: "te", in: namespace)
|
||||
.background(GeometryReader(content: updateHeight))
|
||||
|
||||
NativeTextEditor(
|
||||
text: $composeState.message,
|
||||
disableEditing: $composeState.inProgress,
|
||||
height: teHeight,
|
||||
font: teUiFont,
|
||||
height: $teHeight,
|
||||
focused: $keyboardVisible,
|
||||
alignment: alignment,
|
||||
onImagesAdded: onMediaAdded
|
||||
)
|
||||
.allowsTightening(false)
|
||||
.frame(height: teHeight)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,11 +83,13 @@ struct SendMessageView: View {
|
||||
.frame(height: teHeight, alignment: .bottom)
|
||||
}
|
||||
}
|
||||
|
||||
.padding(.vertical, 1)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerSize: CGSize(width: 20, height: 20))
|
||||
.strokeBorder(.secondary, lineWidth: 0.3, antialiased: true)
|
||||
.frame(height: teHeight)
|
||||
)
|
||||
}
|
||||
.onChange(of: composeState.message, perform: { text in updateFont(text) })
|
||||
.onChange(of: composeState.inProgress) { inProgress in
|
||||
if inProgress {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
||||
@@ -415,16 +400,12 @@ struct SendMessageView: View {
|
||||
.padding([.bottom, .trailing], 4)
|
||||
}
|
||||
|
||||
private func updateHeight(_ g: GeometryProxy) -> Color {
|
||||
private func updateFont(_ text: String) {
|
||||
DispatchQueue.main.async {
|
||||
teHeight = min(max(g.frame(in: .local).size.height, minHeight), maxHeight)
|
||||
(teFont, teUiFont) = isShortEmoji(composeState.message)
|
||||
? composeState.message.count < 4
|
||||
? (largeEmojiFont, largeEmojiUIFont)
|
||||
: (mediumEmojiFont, mediumEmojiUIFont)
|
||||
: (.body, UIFont.preferredFont(forTextStyle: .body))
|
||||
teFont = isShortEmoji(text)
|
||||
? (text.count < 4 ? largeEmojiFont : mediumEmojiFont)
|
||||
: .body
|
||||
}
|
||||
return Color.clear
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ struct AddGroupMembersViewCommon: View {
|
||||
do {
|
||||
for contactId in selectedContacts {
|
||||
let member = try await apiAddMember(groupInfo.groupId, contactId, selectedRole)
|
||||
await MainActor.run { _ = ChatModel.shared.upsertGroupMember(groupInfo, member) }
|
||||
await MainActor.run { _ = chatModel.upsertGroupMember(groupInfo, member) }
|
||||
}
|
||||
addedMembersCb(selectedContacts)
|
||||
} catch {
|
||||
@@ -157,7 +157,7 @@ struct AddGroupMembersViewCommon: View {
|
||||
private func rolePicker() -> some View {
|
||||
Picker("New member role", selection: $selectedRole) {
|
||||
ForEach(GroupMemberRole.allCases) { role in
|
||||
if role <= groupInfo.membership.memberRole {
|
||||
if role <= groupInfo.membership.memberRole && role != .author {
|
||||
Text(role.text)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ struct GroupChatInfoView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@Environment(\.dismiss) var dismiss: DismissAction
|
||||
@ObservedObject var chat: Chat
|
||||
@State var groupInfo: GroupInfo
|
||||
@Binding var groupInfo: GroupInfo
|
||||
@ObservedObject private var alertManager = AlertManager.shared
|
||||
@State private var alert: GroupChatInfoViewAlert? = nil
|
||||
@State private var groupLink: String?
|
||||
@@ -35,14 +35,30 @@ struct GroupChatInfoView: View {
|
||||
case leaveGroupAlert
|
||||
case cantInviteIncognitoAlert
|
||||
case largeGroupReceiptsDisabled
|
||||
case blockMemberAlert(mem: GroupMember)
|
||||
case unblockMemberAlert(mem: GroupMember)
|
||||
case removeMemberAlert(mem: GroupMember)
|
||||
case error(title: LocalizedStringKey, error: LocalizedStringKey)
|
||||
|
||||
var id: GroupChatInfoViewAlert { get { self } }
|
||||
var id: String {
|
||||
switch self {
|
||||
case .deleteGroupAlert: return "deleteGroupAlert"
|
||||
case .clearChatAlert: return "clearChatAlert"
|
||||
case .leaveGroupAlert: return "leaveGroupAlert"
|
||||
case .cantInviteIncognitoAlert: return "cantInviteIncognitoAlert"
|
||||
case .largeGroupReceiptsDisabled: return "largeGroupReceiptsDisabled"
|
||||
case let .blockMemberAlert(mem): return "blockMemberAlert \(mem.groupMemberId)"
|
||||
case let .unblockMemberAlert(mem): return "unblockMemberAlert \(mem.groupMemberId)"
|
||||
case let .removeMemberAlert(mem): return "removeMemberAlert \(mem.groupMemberId)"
|
||||
case let .error(title, _): return "error \(title)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
let members = chatModel.groupMembers
|
||||
.filter { $0.memberStatus != .memLeft && $0.memberStatus != .memRemoved }
|
||||
.filter { m in let status = m.wrapped.memberStatus; return status != .memLeft && status != .memRemoved }
|
||||
.sorted { $0.displayName.lowercased() < $1.displayName.lowercased() }
|
||||
|
||||
List {
|
||||
@@ -57,7 +73,7 @@ struct GroupChatInfoView: View {
|
||||
addOrEditWelcomeMessage()
|
||||
}
|
||||
groupPreferencesButton($groupInfo)
|
||||
if members.filter({ $0.memberCurrent }).count <= SMALL_GROUPS_RCPS_MEM_LIMIT {
|
||||
if members.filter({ $0.wrapped.memberCurrent }).count <= SMALL_GROUPS_RCPS_MEM_LIMIT {
|
||||
sendReceiptsOption()
|
||||
} else {
|
||||
sendReceiptsOptionDisabled()
|
||||
@@ -84,17 +100,17 @@ struct GroupChatInfoView: View {
|
||||
.padding(.leading, 8)
|
||||
}
|
||||
let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase
|
||||
let filteredMembers = s == "" ? members : members.filter { $0.chatViewName.localizedLowercase.contains(s) }
|
||||
memberView(groupInfo.membership, user: true)
|
||||
let filteredMembers = s == "" ? members : members.filter { $0.wrapped.chatViewName.localizedLowercase.contains(s) }
|
||||
MemberRowView(groupInfo: groupInfo, groupMember: GMember(groupInfo.membership), user: true, alert: $alert)
|
||||
ForEach(filteredMembers) { member in
|
||||
ZStack {
|
||||
NavigationLink {
|
||||
memberInfoView(member.groupMemberId)
|
||||
memberInfoView(member)
|
||||
} label: {
|
||||
EmptyView()
|
||||
}
|
||||
.opacity(0)
|
||||
memberView(member)
|
||||
MemberRowView(groupInfo: groupInfo, groupMember: member, alert: $alert)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,6 +142,10 @@ struct GroupChatInfoView: View {
|
||||
case .leaveGroupAlert: return leaveGroupAlert()
|
||||
case .cantInviteIncognitoAlert: return cantInviteIncognitoAlert()
|
||||
case .largeGroupReceiptsDisabled: return largeGroupReceiptsDisabledAlert()
|
||||
case let .blockMemberAlert(mem): return blockMemberAlert(groupInfo, mem)
|
||||
case let .unblockMemberAlert(mem): return unblockMemberAlert(groupInfo, mem)
|
||||
case let .removeMemberAlert(mem): return removeMemberAlert(mem)
|
||||
case let .error(title, error): return Alert(title: Text(title), message: Text(error))
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
@@ -174,7 +194,7 @@ struct GroupChatInfoView: View {
|
||||
Task {
|
||||
let groupMembers = await apiListMembers(groupInfo.groupId)
|
||||
await MainActor.run {
|
||||
ChatModel.shared.groupMembers = groupMembers
|
||||
chatModel.groupMembers = groupMembers.map { GMember.init($0) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,51 +203,92 @@ struct GroupChatInfoView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func memberView(_ member: GroupMember, user: Bool = false) -> some View {
|
||||
HStack{
|
||||
ProfileImage(imageStr: member.image)
|
||||
.frame(width: 38, height: 38)
|
||||
.padding(.trailing, 2)
|
||||
// TODO server connection status
|
||||
VStack(alignment: .leading) {
|
||||
let t = Text(member.chatViewName).foregroundColor(member.memberIncognito ? .indigo : .primary)
|
||||
(member.verified ? memberVerifiedShield + t : t)
|
||||
.lineLimit(1)
|
||||
let s = Text(member.memberStatus.shortText)
|
||||
(user ? Text ("you: ") + s : s)
|
||||
.lineLimit(1)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
private struct MemberRowView: View {
|
||||
var groupInfo: GroupInfo
|
||||
@ObservedObject var groupMember: GMember
|
||||
var user: Bool = false
|
||||
@Binding var alert: GroupChatInfoViewAlert?
|
||||
|
||||
var body: some View {
|
||||
let member = groupMember.wrapped
|
||||
let v = HStack{
|
||||
ProfileImage(imageStr: member.image)
|
||||
.frame(width: 38, height: 38)
|
||||
.padding(.trailing, 2)
|
||||
// TODO server connection status
|
||||
VStack(alignment: .leading) {
|
||||
let t = Text(member.chatViewName).foregroundColor(member.memberIncognito ? .indigo : .primary)
|
||||
(member.verified ? memberVerifiedShield + t : t)
|
||||
.lineLimit(1)
|
||||
let s = Text(member.memberStatus.shortText)
|
||||
(user ? Text ("you: ") + s : s)
|
||||
.lineLimit(1)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
Spacer()
|
||||
let role = member.memberRole
|
||||
if role == .owner || role == .admin {
|
||||
Text(member.memberRole.text)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
let role = member.memberRole
|
||||
if role == .owner || role == .admin {
|
||||
Text(member.memberRole.text)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
if user {
|
||||
v
|
||||
} else if member.canBeRemoved(groupInfo: groupInfo) {
|
||||
removeSwipe(member, blockSwipe(member, v))
|
||||
} else {
|
||||
blockSwipe(member, v)
|
||||
}
|
||||
}
|
||||
|
||||
private func blockSwipe<V: View>(_ member: GroupMember, _ v: V) -> some View {
|
||||
v.swipeActions(edge: .leading) {
|
||||
if member.memberSettings.showMessages {
|
||||
Button {
|
||||
alert = .blockMemberAlert(mem: member)
|
||||
} label: {
|
||||
Label("Block member", systemImage: "hand.raised").foregroundColor(.secondary)
|
||||
}
|
||||
} else {
|
||||
Button {
|
||||
alert = .unblockMemberAlert(mem: member)
|
||||
} label: {
|
||||
Label("Unblock member", systemImage: "hand.raised.slash").foregroundColor(.accentColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func removeSwipe<V: View>(_ member: GroupMember, _ v: V) -> some View {
|
||||
v.swipeActions(edge: .trailing) {
|
||||
Button(role: .destructive) {
|
||||
alert = .removeMemberAlert(mem: member)
|
||||
} label: {
|
||||
Label("Remove member", systemImage: "trash")
|
||||
.foregroundColor(Color.red)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var memberVerifiedShield: Text {
|
||||
(Text(Image(systemName: "checkmark.shield")) + Text(" "))
|
||||
.font(.caption)
|
||||
.baselineOffset(2)
|
||||
.kerning(-2)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
@ViewBuilder private func memberInfoView(_ groupMemberId: Int64?) -> some View {
|
||||
if let mId = groupMemberId, let member = chatModel.groupMembers.first(where: { $0.groupMemberId == mId }) {
|
||||
GroupMemberInfoView(groupInfo: groupInfo, member: member)
|
||||
.navigationBarHidden(false)
|
||||
}
|
||||
private func memberInfoView(_ groupMember: GMember) -> some View {
|
||||
GroupMemberInfoView(groupInfo: groupInfo, groupMember: groupMember)
|
||||
.navigationBarHidden(false)
|
||||
}
|
||||
|
||||
private func groupLinkButton() -> some View {
|
||||
NavigationLink {
|
||||
GroupLinkView(groupId: groupInfo.groupId, groupLink: $groupLink, groupLinkMemberRole: $groupLinkMemberRole)
|
||||
.navigationBarTitle("Group link")
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
GroupLinkView(
|
||||
groupId: groupInfo.groupId,
|
||||
groupLink: $groupLink,
|
||||
groupLinkMemberRole: $groupLinkMemberRole,
|
||||
showTitle: false,
|
||||
creatingGroup: false
|
||||
)
|
||||
.navigationBarTitle("Group link")
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
} label: {
|
||||
if groupLink == nil {
|
||||
Label("Create group link", systemImage: "link.badge.plus")
|
||||
@@ -375,6 +436,28 @@ struct GroupChatInfoView: View {
|
||||
alert = .largeGroupReceiptsDisabled
|
||||
}
|
||||
}
|
||||
|
||||
private func removeMemberAlert(_ mem: GroupMember) -> Alert {
|
||||
Alert(
|
||||
title: Text("Remove member?"),
|
||||
message: Text("Member will be removed from group - this cannot be undone!"),
|
||||
primaryButton: .destructive(Text("Remove")) {
|
||||
Task {
|
||||
do {
|
||||
let updatedMember = try await apiRemoveMember(groupInfo.groupId, mem.groupMemberId)
|
||||
await MainActor.run {
|
||||
_ = chatModel.upsertGroupMember(groupInfo, updatedMember)
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("apiRemoveMember error: \(responseError(error))")
|
||||
let a = getErrorAlert(error, "Error removing member")
|
||||
alert = .error(title: a.title, error: a.message)
|
||||
}
|
||||
}
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func groupPreferencesButton(_ groupInfo: Binding<GroupInfo>, _ creatingGroup: Bool = false) -> some View {
|
||||
@@ -396,6 +479,14 @@ func groupPreferencesButton(_ groupInfo: Binding<GroupInfo>, _ creatingGroup: Bo
|
||||
}
|
||||
}
|
||||
|
||||
private var memberVerifiedShield: Text {
|
||||
(Text(Image(systemName: "checkmark.shield")) + Text(" "))
|
||||
.font(.caption)
|
||||
.baselineOffset(2)
|
||||
.kerning(-2)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
func cantInviteIncognitoAlert() -> Alert {
|
||||
Alert(
|
||||
title: Text("Can't invite contacts!"),
|
||||
@@ -412,6 +503,9 @@ func largeGroupReceiptsDisabledAlert() -> Alert {
|
||||
|
||||
struct GroupChatInfoView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
GroupChatInfoView(chat: Chat(chatInfo: ChatInfo.sampleData.group, chatItems: []), groupInfo: GroupInfo.sampleData)
|
||||
GroupChatInfoView(
|
||||
chat: Chat(chatInfo: ChatInfo.sampleData.group, chatItems: []),
|
||||
groupInfo: Binding.constant(GroupInfo.sampleData)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@ struct GroupLinkView: View {
|
||||
var groupId: Int64
|
||||
@Binding var groupLink: String?
|
||||
@Binding var groupLinkMemberRole: GroupMemberRole
|
||||
var showTitle: Bool = false
|
||||
var creatingGroup: Bool = false
|
||||
var linkCreatedCb: (() -> Void)? = nil
|
||||
@State private var creatingLink = false
|
||||
@State private var alert: GroupLinkAlert?
|
||||
|
||||
@@ -29,10 +32,35 @@ struct GroupLinkView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if creatingGroup {
|
||||
NavigationView {
|
||||
groupLinkView()
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button ("Continue") { linkCreatedCb?() }
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
groupLinkView()
|
||||
}
|
||||
}
|
||||
|
||||
private func groupLinkView() -> some View {
|
||||
List {
|
||||
Text("You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it.")
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||
Group {
|
||||
if showTitle {
|
||||
Text("Group link")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
Text("You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it.")
|
||||
}
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||
|
||||
Section {
|
||||
if let groupLink = groupLink {
|
||||
Picker("Initial role", selection: $groupLinkMemberRole) {
|
||||
@@ -48,8 +76,10 @@ struct GroupLinkView: View {
|
||||
Label("Share link", systemImage: "square.and.arrow.up")
|
||||
}
|
||||
|
||||
Button(role: .destructive) { alert = .deleteLink } label: {
|
||||
Label("Delete link", systemImage: "trash")
|
||||
if !creatingGroup {
|
||||
Button(role: .destructive) { alert = .deleteLink } label: {
|
||||
Label("Delete link", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Button(action: createGroupLink) {
|
||||
|
||||
@@ -12,8 +12,8 @@ import SimpleXChat
|
||||
struct GroupMemberInfoView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@Environment(\.dismiss) var dismiss: DismissAction
|
||||
var groupInfo: GroupInfo
|
||||
@State var member: GroupMember
|
||||
@State var groupInfo: GroupInfo
|
||||
@ObservedObject var groupMember: GMember
|
||||
var navigation: Bool = false
|
||||
@State private var connectionStats: ConnectionStats? = nil
|
||||
@State private var connectionCode: String? = nil
|
||||
@@ -25,6 +25,8 @@ struct GroupMemberInfoView: View {
|
||||
@State private var progressIndicator = false
|
||||
|
||||
enum GroupMemberInfoViewAlert: Identifiable {
|
||||
case blockMemberAlert(mem: GroupMember)
|
||||
case unblockMemberAlert(mem: GroupMember)
|
||||
case removeMemberAlert(mem: GroupMember)
|
||||
case changeMemberRoleAlert(mem: GroupMember, role: GroupMemberRole)
|
||||
case switchAddressAlert
|
||||
@@ -35,8 +37,10 @@ struct GroupMemberInfoView: View {
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case .removeMemberAlert: return "removeMemberAlert"
|
||||
case let .changeMemberRoleAlert(_, role): return "changeMemberRoleAlert \(role.rawValue)"
|
||||
case let .blockMemberAlert(mem): return "blockMemberAlert \(mem.groupMemberId)"
|
||||
case let .unblockMemberAlert(mem): return "unblockMemberAlert \(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"
|
||||
case .abortSwitchAddressAlert: return "abortSwitchAddressAlert"
|
||||
case .syncConnectionForceAlert: return "syncConnectionForceAlert"
|
||||
@@ -66,6 +70,7 @@ struct GroupMemberInfoView: View {
|
||||
private func groupMemberInfoView() -> some View {
|
||||
ZStack {
|
||||
VStack {
|
||||
let member = groupMember.wrapped
|
||||
List {
|
||||
groupMemberInfoHeader(member)
|
||||
.listRowBackground(Color.clear)
|
||||
@@ -159,9 +164,14 @@ struct GroupMemberInfoView: View {
|
||||
}
|
||||
}
|
||||
|
||||
if member.canBeRemoved(groupInfo: groupInfo) {
|
||||
Section {
|
||||
removeMemberButton(member)
|
||||
Section {
|
||||
if member.memberSettings.showMessages {
|
||||
blockMemberButton(member)
|
||||
} else {
|
||||
unblockMemberButton(member)
|
||||
}
|
||||
if member.canBeRemoved(groupInfo: groupInfo) {
|
||||
removeMemberButton(member)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +192,7 @@ struct GroupMemberInfoView: View {
|
||||
do {
|
||||
let (_, stats) = try apiGroupMemberInfo(groupInfo.apiId, member.groupMemberId)
|
||||
let (mem, code) = member.memberActive ? try apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId) : (member, nil)
|
||||
member = mem
|
||||
_ = chatModel.upsertGroupMember(groupInfo, mem)
|
||||
connectionStats = stats
|
||||
connectionCode = code
|
||||
} catch let error {
|
||||
@@ -190,15 +200,20 @@ struct GroupMemberInfoView: View {
|
||||
}
|
||||
justOpened = false
|
||||
}
|
||||
.onChange(of: newRole) { _ in
|
||||
.onChange(of: newRole) { newRole in
|
||||
if newRole != member.memberRole {
|
||||
alert = .changeMemberRoleAlert(mem: member, role: newRole)
|
||||
}
|
||||
}
|
||||
.onChange(of: member.memberRole) { role in
|
||||
newRole = role
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
||||
.alert(item: $alert) { alertItem in
|
||||
switch(alertItem) {
|
||||
case let .blockMemberAlert(mem): return blockMemberAlert(groupInfo, mem)
|
||||
case let .unblockMemberAlert(mem): return unblockMemberAlert(groupInfo, mem)
|
||||
case let .removeMemberAlert(mem): return removeMemberAlert(mem)
|
||||
case let .changeMemberRoleAlert(mem, _): return changeMemberRoleAlert(mem)
|
||||
case .switchAddressAlert: return switchAddressAlert(switchMemberAddress)
|
||||
@@ -263,7 +278,7 @@ struct GroupMemberInfoView: View {
|
||||
progressIndicator = true
|
||||
Task {
|
||||
do {
|
||||
let memberContact = try await apiCreateMemberContact(groupInfo.apiId, member.groupMemberId)
|
||||
let memberContact = try await apiCreateMemberContact(groupInfo.apiId, groupMember.groupMemberId)
|
||||
await MainActor.run {
|
||||
progressIndicator = false
|
||||
chatModel.addChat(Chat(chatInfo: .direct(contact: memberContact)))
|
||||
@@ -321,20 +336,20 @@ struct GroupMemberInfoView: View {
|
||||
}
|
||||
|
||||
private func verifyCodeButton(_ code: String) -> some View {
|
||||
NavigationLink {
|
||||
let member = groupMember.wrapped
|
||||
return NavigationLink {
|
||||
VerifyCodeView(
|
||||
displayName: member.displayName,
|
||||
connectionCode: code,
|
||||
connectionVerified: member.verified,
|
||||
verify: { code in
|
||||
var member = groupMember.wrapped
|
||||
if let r = apiVerifyGroupMember(member.groupId, member.groupMemberId, connectionCode: code) {
|
||||
let (verified, existingCode) = r
|
||||
let connCode = verified ? SecurityCode(securityCode: existingCode, verifiedAt: .now) : nil
|
||||
connectionCode = existingCode
|
||||
member.activeConn?.connectionCode = connCode
|
||||
if let i = chatModel.groupMembers.firstIndex(where: { $0.groupMemberId == member.groupMemberId }) {
|
||||
chatModel.groupMembers[i].activeConn?.connectionCode = connCode
|
||||
}
|
||||
_ = chatModel.upsertGroupMember(groupInfo, member)
|
||||
return r
|
||||
}
|
||||
return nil
|
||||
@@ -368,12 +383,29 @@ struct GroupMemberInfoView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func blockMemberButton(_ mem: GroupMember) -> some View {
|
||||
Button(role: .destructive) {
|
||||
alert = .blockMemberAlert(mem: mem)
|
||||
} label: {
|
||||
Label("Block member", systemImage: "hand.raised")
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
|
||||
private func unblockMemberButton(_ mem: GroupMember) -> some View {
|
||||
Button {
|
||||
alert = .unblockMemberAlert(mem: mem)
|
||||
} label: {
|
||||
Label("Unblock member", systemImage: "hand.raised.slash")
|
||||
}
|
||||
}
|
||||
|
||||
private func removeMemberButton(_ mem: GroupMember) -> some View {
|
||||
Button(role: .destructive) {
|
||||
alert = .removeMemberAlert(mem: mem)
|
||||
} label: {
|
||||
Label("Remove member", systemImage: "trash")
|
||||
.foregroundColor(Color.red)
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,7 +441,6 @@ struct GroupMemberInfoView: View {
|
||||
do {
|
||||
let updatedMember = try await apiMemberRole(groupInfo.groupId, mem.groupMemberId, newRole)
|
||||
await MainActor.run {
|
||||
member = updatedMember
|
||||
_ = chatModel.upsertGroupMember(groupInfo, updatedMember)
|
||||
}
|
||||
|
||||
@@ -430,10 +461,10 @@ struct GroupMemberInfoView: View {
|
||||
private func switchMemberAddress() {
|
||||
Task {
|
||||
do {
|
||||
let stats = try apiSwitchGroupMember(groupInfo.apiId, member.groupMemberId)
|
||||
let stats = try apiSwitchGroupMember(groupInfo.apiId, groupMember.groupMemberId)
|
||||
connectionStats = stats
|
||||
await MainActor.run {
|
||||
chatModel.updateGroupMemberConnectionStats(groupInfo, member, stats)
|
||||
chatModel.updateGroupMemberConnectionStats(groupInfo, groupMember.wrapped, stats)
|
||||
dismiss()
|
||||
}
|
||||
} catch let error {
|
||||
@@ -449,10 +480,10 @@ struct GroupMemberInfoView: View {
|
||||
private func abortSwitchMemberAddress() {
|
||||
Task {
|
||||
do {
|
||||
let stats = try apiAbortSwitchGroupMember(groupInfo.apiId, member.groupMemberId)
|
||||
let stats = try apiAbortSwitchGroupMember(groupInfo.apiId, groupMember.groupMemberId)
|
||||
connectionStats = stats
|
||||
await MainActor.run {
|
||||
chatModel.updateGroupMemberConnectionStats(groupInfo, member, stats)
|
||||
chatModel.updateGroupMemberConnectionStats(groupInfo, groupMember.wrapped, stats)
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("abortSwitchMemberAddress apiAbortSwitchGroupMember error: \(responseError(error))")
|
||||
@@ -467,7 +498,7 @@ struct GroupMemberInfoView: View {
|
||||
private func syncMemberConnection(force: Bool) {
|
||||
Task {
|
||||
do {
|
||||
let (mem, stats) = try apiSyncGroupMemberRatchet(groupInfo.apiId, member.groupMemberId, force)
|
||||
let (mem, stats) = try apiSyncGroupMemberRatchet(groupInfo.apiId, groupMember.groupMemberId, force)
|
||||
connectionStats = stats
|
||||
await MainActor.run {
|
||||
chatModel.updateGroupMemberConnectionStats(groupInfo, mem, stats)
|
||||
@@ -484,11 +515,54 @@ struct GroupMemberInfoView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func blockMemberAlert(_ gInfo: GroupInfo, _ mem: GroupMember) -> Alert {
|
||||
Alert(
|
||||
title: Text("Block member?"),
|
||||
message: Text("All new messages from \(mem.chatViewName) will be hidden!"),
|
||||
primaryButton: .destructive(Text("Block")) {
|
||||
toggleShowMemberMessages(gInfo, mem, false)
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
|
||||
func unblockMemberAlert(_ gInfo: GroupInfo, _ mem: GroupMember) -> Alert {
|
||||
Alert(
|
||||
title: Text("Unblock member?"),
|
||||
message: Text("Messages from \(mem.chatViewName) will be shown!"),
|
||||
primaryButton: .default(Text("Unblock")) {
|
||||
toggleShowMemberMessages(gInfo, mem, true)
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
|
||||
func toggleShowMemberMessages(_ gInfo: GroupInfo, _ member: GroupMember, _ showMessages: Bool) {
|
||||
var memberSettings = member.memberSettings
|
||||
memberSettings.showMessages = showMessages
|
||||
updateMemberSettings(gInfo, member, memberSettings)
|
||||
}
|
||||
|
||||
func updateMemberSettings(_ gInfo: GroupInfo, _ member: GroupMember, _ memberSettings: GroupMemberSettings) {
|
||||
Task {
|
||||
do {
|
||||
try await apiSetMemberSettings(gInfo.groupId, member.groupMemberId, memberSettings)
|
||||
await MainActor.run {
|
||||
var mem = member
|
||||
mem.memberSettings = memberSettings
|
||||
_ = ChatModel.shared.upsertGroupMember(gInfo, mem)
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("apiSetMemberSettings error \(responseError(error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct GroupMemberInfoView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
GroupMemberInfoView(
|
||||
groupInfo: GroupInfo.sampleData,
|
||||
member: GroupMember.sampleData
|
||||
groupMember: GMember.sampleData
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,52 +33,86 @@ struct ChatListNavLink: View {
|
||||
@State private var showContactConnectionInfo = false
|
||||
@State private var showInvalidJSON = false
|
||||
@State private var showDeleteContactActionSheet = false
|
||||
@State private var showConnectContactViaAddressDialog = false
|
||||
@State private var inProgress = false
|
||||
@State private var progressByTimeout = false
|
||||
|
||||
var body: some View {
|
||||
switch chat.chatInfo {
|
||||
case let .direct(contact):
|
||||
contactNavLink(contact)
|
||||
case let .group(groupInfo):
|
||||
groupNavLink(groupInfo)
|
||||
case let .contactRequest(cReq):
|
||||
contactRequestNavLink(cReq)
|
||||
case let .contactConnection(cConn):
|
||||
contactConnectionNavLink(cConn)
|
||||
case let .invalidJSON(json):
|
||||
invalidJSONPreview(json)
|
||||
Group {
|
||||
switch chat.chatInfo {
|
||||
case let .direct(contact):
|
||||
contactNavLink(contact)
|
||||
case let .group(groupInfo):
|
||||
groupNavLink(groupInfo)
|
||||
case let .contactRequest(cReq):
|
||||
contactRequestNavLink(cReq)
|
||||
case let .contactConnection(cConn):
|
||||
contactConnectionNavLink(cConn)
|
||||
case let .invalidJSON(json):
|
||||
invalidJSONPreview(json)
|
||||
}
|
||||
}
|
||||
.onChange(of: inProgress) { inProgress in
|
||||
if inProgress {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
progressByTimeout = inProgress
|
||||
}
|
||||
} else {
|
||||
progressByTimeout = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func contactNavLink(_ contact: Contact) -> some View {
|
||||
NavLinkPlain(
|
||||
tag: chat.chatInfo.id,
|
||||
selection: $chatModel.chatId,
|
||||
label: { ChatPreviewView(chat: chat) }
|
||||
)
|
||||
.swipeActions(edge: .leading, allowsFullSwipe: true) {
|
||||
markReadButton()
|
||||
toggleFavoriteButton()
|
||||
toggleNtfsButton(chat)
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
if !chat.chatItems.isEmpty {
|
||||
clearChatButton()
|
||||
}
|
||||
Button {
|
||||
if contact.ready || !contact.active {
|
||||
showDeleteContactActionSheet = true
|
||||
} else {
|
||||
AlertManager.shared.showAlert(deletePendingContactAlert(chat, contact))
|
||||
Group {
|
||||
if contact.activeConn == nil && contact.profile.contactLink != nil {
|
||||
ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false))
|
||||
.frame(height: rowHeights[dynamicTypeSize])
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
Button {
|
||||
showDeleteContactActionSheet = true
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
.onTapGesture { showConnectContactViaAddressDialog = true }
|
||||
.confirmationDialog("Connect with \(contact.chatViewName)", isPresented: $showConnectContactViaAddressDialog, titleVisibility: .visible) {
|
||||
Button("Use current profile") { connectContactViaAddress_(contact, false) }
|
||||
Button("Use new incognito profile") { connectContactViaAddress_(contact, true) }
|
||||
}
|
||||
} else {
|
||||
NavLinkPlain(
|
||||
tag: chat.chatInfo.id,
|
||||
selection: $chatModel.chatId,
|
||||
label: { ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) }
|
||||
)
|
||||
.swipeActions(edge: .leading, allowsFullSwipe: true) {
|
||||
markReadButton()
|
||||
toggleFavoriteButton()
|
||||
toggleNtfsButton(chat)
|
||||
}
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
if !chat.chatItems.isEmpty {
|
||||
clearChatButton()
|
||||
}
|
||||
Button {
|
||||
if contact.ready || !contact.active {
|
||||
showDeleteContactActionSheet = true
|
||||
} else {
|
||||
AlertManager.shared.showAlert(deletePendingContactAlert(chat, contact))
|
||||
}
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
.frame(height: rowHeights[dynamicTypeSize])
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
.frame(height: rowHeights[dynamicTypeSize])
|
||||
.actionSheet(isPresented: $showDeleteContactActionSheet) {
|
||||
if contact.ready && contact.active {
|
||||
ActionSheet(
|
||||
return ActionSheet(
|
||||
title: Text("Delete contact?\nThis cannot be undone!"),
|
||||
buttons: [
|
||||
.destructive(Text("Delete and notify contact")) { Task { await deleteChat(chat, notify: true) } },
|
||||
@@ -87,7 +121,7 @@ struct ChatListNavLink: View {
|
||||
]
|
||||
)
|
||||
} else {
|
||||
ActionSheet(
|
||||
return ActionSheet(
|
||||
title: Text("Delete contact?\nThis cannot be undone!"),
|
||||
buttons: [
|
||||
.destructive(Text("Delete")) { Task { await deleteChat(chat) } },
|
||||
@@ -101,7 +135,7 @@ struct ChatListNavLink: View {
|
||||
@ViewBuilder private func groupNavLink(_ groupInfo: GroupInfo) -> some View {
|
||||
switch (groupInfo.membership.memberStatus) {
|
||||
case .memInvited:
|
||||
ChatPreviewView(chat: chat)
|
||||
ChatPreviewView(chat: chat, progressByTimeout: $progressByTimeout)
|
||||
.frame(height: rowHeights[dynamicTypeSize])
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
joinGroupButton()
|
||||
@@ -112,12 +146,16 @@ struct ChatListNavLink: View {
|
||||
.onTapGesture { showJoinGroupDialog = true }
|
||||
.confirmationDialog("Group invitation", isPresented: $showJoinGroupDialog, titleVisibility: .visible) {
|
||||
Button(chat.chatInfo.incognito ? "Join incognito" : "Join group") {
|
||||
joinGroup(groupInfo.groupId)
|
||||
inProgress = true
|
||||
joinGroup(groupInfo.groupId) {
|
||||
await MainActor.run { inProgress = false }
|
||||
}
|
||||
}
|
||||
Button("Delete invitation", role: .destructive) { Task { await deleteChat(chat) } }
|
||||
}
|
||||
.disabled(inProgress)
|
||||
case .memAccepted:
|
||||
ChatPreviewView(chat: chat)
|
||||
ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false))
|
||||
.frame(height: rowHeights[dynamicTypeSize])
|
||||
.onTapGesture {
|
||||
AlertManager.shared.showAlert(groupInvitationAcceptedAlert())
|
||||
@@ -134,7 +172,7 @@ struct ChatListNavLink: View {
|
||||
NavLinkPlain(
|
||||
tag: chat.chatInfo.id,
|
||||
selection: $chatModel.chatId,
|
||||
label: { ChatPreviewView(chat: chat) },
|
||||
label: { ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) },
|
||||
disabled: !groupInfo.ready
|
||||
)
|
||||
.frame(height: rowHeights[dynamicTypeSize])
|
||||
@@ -159,7 +197,10 @@ struct ChatListNavLink: View {
|
||||
|
||||
private func joinGroupButton() -> some View {
|
||||
Button {
|
||||
joinGroup(chat.chatInfo.apiId)
|
||||
inProgress = true
|
||||
joinGroup(chat.chatInfo.apiId) {
|
||||
await MainActor.run { inProgress = false }
|
||||
}
|
||||
} label: {
|
||||
Label("Join", systemImage: chat.chatInfo.incognito ? "theatermasks" : "ipad.and.arrow.forward")
|
||||
}
|
||||
@@ -391,6 +432,17 @@ struct ChatListNavLink: View {
|
||||
.environment(\EnvironmentValues.refresh as! WritableKeyPath<EnvironmentValues, RefreshAction?>, nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func connectContactViaAddress_(_ contact: Contact, _ incognito: Bool) {
|
||||
Task {
|
||||
let ok = await connectContactViaAddress(contact.contactId, incognito)
|
||||
if ok {
|
||||
await MainActor.run {
|
||||
chatModel.chatId = contact.id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func deleteContactConnectionAlert(_ contactConnection: PendingContactConnection, showError: @escaping (ErrorAlert) -> Void, success: @escaping () -> Void = {}) -> Alert {
|
||||
@@ -419,7 +471,22 @@ func deleteContactConnectionAlert(_ contactConnection: PendingContactConnection,
|
||||
)
|
||||
}
|
||||
|
||||
func joinGroup(_ groupId: Int64) {
|
||||
func connectContactViaAddress(_ contactId: Int64, _ incognito: Bool) async -> Bool {
|
||||
let (contact, alert) = await apiConnectContactViaAddress(incognito: incognito, contactId: contactId)
|
||||
if let alert = alert {
|
||||
AlertManager.shared.showAlert(alert)
|
||||
return false
|
||||
} else if let contact = contact {
|
||||
await MainActor.run {
|
||||
ChatModel.shared.updateContact(contact)
|
||||
AlertManager.shared.showAlert(connReqSentAlert(.contact))
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func joinGroup(_ groupId: Int64, _ onComplete: @escaping () async -> Void) {
|
||||
Task {
|
||||
logger.debug("joinGroup")
|
||||
do {
|
||||
@@ -434,7 +501,9 @@ func joinGroup(_ groupId: Int64) {
|
||||
AlertManager.shared.showAlertMsg(title: "No group!", message: "This group no longer exists.")
|
||||
await deleteGroup()
|
||||
}
|
||||
await onComplete()
|
||||
} catch let error {
|
||||
await onComplete()
|
||||
let a = getErrorAlert(error, "Error joining group")
|
||||
AlertManager.shared.showAlertMsg(title: a.title, message: a.message)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ struct ChatListView: View {
|
||||
@State private var searchText = ""
|
||||
@State private var showAddChat = false
|
||||
@State private var userPickerVisible = false
|
||||
@State private var showConnectDesktop = false
|
||||
@AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false
|
||||
|
||||
var body: some View {
|
||||
@@ -29,7 +30,7 @@ struct ChatListView: View {
|
||||
ZStack(alignment: .topLeading) {
|
||||
NavStackCompat(
|
||||
isActive: Binding(
|
||||
get: { ChatModel.shared.chatId != nil },
|
||||
get: { chatModel.chatId != nil },
|
||||
set: { _ in }
|
||||
),
|
||||
destination: chatView
|
||||
@@ -48,7 +49,14 @@ struct ChatListView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
UserPicker(showSettings: $showSettings, userPickerVisible: $userPickerVisible)
|
||||
UserPicker(
|
||||
showSettings: $showSettings,
|
||||
showConnectDesktop: $showConnectDesktop,
|
||||
userPickerVisible: $userPickerVisible
|
||||
)
|
||||
}
|
||||
.sheet(isPresented: $showConnectDesktop) {
|
||||
ConnectDesktopView()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,13 +185,6 @@ struct ChatListView: View {
|
||||
showAddChat = true
|
||||
}
|
||||
|
||||
connectButton("or chat with the developers") {
|
||||
DispatchQueue.main.async {
|
||||
UIApplication.shared.open(simplexTeamURL)
|
||||
}
|
||||
}
|
||||
.padding(.top, 10)
|
||||
|
||||
Spacer()
|
||||
Text("You have no chats")
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
@@ -12,6 +12,7 @@ import SimpleXChat
|
||||
struct ChatPreviewView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@ObservedObject var chat: Chat
|
||||
@Binding var progressByTimeout: Bool
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
var darkGreen = Color(red: 0, green: 0.5, blue: 0)
|
||||
|
||||
@@ -189,7 +190,10 @@ struct ChatPreviewView: View {
|
||||
} else {
|
||||
switch (chat.chatInfo) {
|
||||
case let .direct(contact):
|
||||
if !contact.ready {
|
||||
if contact.activeConn == nil && contact.profile.contactLink != nil {
|
||||
chatPreviewInfoText("Tap to Connect")
|
||||
.foregroundColor(.accentColor)
|
||||
} else if !contact.ready && contact.activeConn != nil {
|
||||
if contact.nextSendGrpInv {
|
||||
chatPreviewInfoText("send direct message")
|
||||
} else if contact.active {
|
||||
@@ -237,7 +241,7 @@ struct ChatPreviewView: View {
|
||||
@ViewBuilder private func chatStatusImage() -> some View {
|
||||
switch chat.chatInfo {
|
||||
case let .direct(contact):
|
||||
if contact.active {
|
||||
if contact.active && contact.activeConn != nil {
|
||||
switch (chatModel.contactNetworkStatus(contact)) {
|
||||
case .connected: incognitoIcon(chat.chatInfo.incognito)
|
||||
case .error:
|
||||
@@ -252,6 +256,12 @@ struct ChatPreviewView: View {
|
||||
} else {
|
||||
incognitoIcon(chat.chatInfo.incognito)
|
||||
}
|
||||
case .group:
|
||||
if progressByTimeout {
|
||||
ProgressView()
|
||||
} else {
|
||||
incognitoIcon(chat.chatInfo.incognito)
|
||||
}
|
||||
default:
|
||||
incognitoIcon(chat.chatInfo.incognito)
|
||||
}
|
||||
@@ -280,30 +290,30 @@ struct ChatPreviewView_Previews: PreviewProvider {
|
||||
ChatPreviewView(chat: Chat(
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
chatItems: []
|
||||
))
|
||||
), progressByTimeout: Binding.constant(false))
|
||||
ChatPreviewView(chat: Chat(
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete))]
|
||||
))
|
||||
), progressByTimeout: Binding.constant(false))
|
||||
ChatPreviewView(chat: Chat(
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete))],
|
||||
chatStats: ChatStats(unreadCount: 11, minUnreadItemId: 0)
|
||||
))
|
||||
), progressByTimeout: Binding.constant(false))
|
||||
ChatPreviewView(chat: Chat(
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now))]
|
||||
))
|
||||
), progressByTimeout: Binding.constant(false))
|
||||
ChatPreviewView(chat: Chat(
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete))],
|
||||
chatStats: ChatStats(unreadCount: 3, minUnreadItemId: 0)
|
||||
))
|
||||
), progressByTimeout: Binding.constant(false))
|
||||
ChatPreviewView(chat: Chat(
|
||||
chatInfo: ChatInfo.sampleData.group,
|
||||
chatItems: [ChatItem.getSample(1, .directSnd, .now, "Lorem ipsum dolor sit amet, d. consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")],
|
||||
chatStats: ChatStats(unreadCount: 11, minUnreadItemId: 0)
|
||||
))
|
||||
), progressByTimeout: Binding.constant(false))
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 78))
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ struct ContactConnectionInfo: View {
|
||||
if let conn = try await apiSetConnectionAlias(connId: contactConnection.pccConnId, localAlias: localAlias) {
|
||||
await MainActor.run {
|
||||
contactConnection = conn
|
||||
ChatModel.shared.updateContactConnection(conn)
|
||||
m.updateContactConnection(conn)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ struct UserPicker: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@Binding var showSettings: Bool
|
||||
@Binding var showConnectDesktop: Bool
|
||||
@Binding var userPickerVisible: Bool
|
||||
@State var scrollViewContentSize: CGSize = .zero
|
||||
@State var disableScrolling: Bool = true
|
||||
@@ -62,6 +63,13 @@ struct UserPicker: View {
|
||||
.simultaneousGesture(DragGesture(minimumDistance: disableScrolling ? 0 : 10000000))
|
||||
.frame(maxHeight: scrollViewContentSize.height)
|
||||
|
||||
menuButton("Use from desktop", icon: "desktopcomputer") {
|
||||
showConnectDesktop = true
|
||||
withAnimation {
|
||||
userPickerVisible.toggle()
|
||||
}
|
||||
}
|
||||
Divider()
|
||||
menuButton("Settings", icon: "gearshape") {
|
||||
showSettings = true
|
||||
withAnimation {
|
||||
@@ -85,7 +93,7 @@ struct UserPicker: View {
|
||||
do {
|
||||
m.users = try listUsers()
|
||||
} catch let error {
|
||||
logger.error("Error updating users \(responseError(error))")
|
||||
logger.error("Error loading users \(responseError(error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,7 +152,8 @@ struct UserPicker: View {
|
||||
.overlay(DetermineWidth())
|
||||
Spacer()
|
||||
Image(systemName: icon)
|
||||
// .frame(width: 24, alignment: .center)
|
||||
.symbolRenderingMode(.monochrome)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.vertical, 22)
|
||||
@@ -170,6 +179,7 @@ struct UserPicker_Previews: PreviewProvider {
|
||||
m.users = [UserInfo.sampleData, UserInfo.sampleData]
|
||||
return UserPicker(
|
||||
showSettings: Binding.constant(false),
|
||||
showConnectDesktop: Binding.constant(false),
|
||||
userPickerVisible: Binding.constant(true)
|
||||
)
|
||||
.environmentObject(m)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import AVKit
|
||||
import Combine
|
||||
|
||||
struct VideoPlayerView: UIViewRepresentable {
|
||||
|
||||
@@ -37,6 +38,14 @@ struct VideoPlayerView: UIViewRepresentable {
|
||||
player.seek(to: CMTime.zero)
|
||||
player.play()
|
||||
}
|
||||
var played = false
|
||||
context.coordinator.publisher = player.publisher(for: \.timeControlStatus).sink { status in
|
||||
if played || status == .playing {
|
||||
AppDelegate.keepScreenOn(status == .playing)
|
||||
AudioPlayer.changeAudioSession(status == .playing)
|
||||
}
|
||||
played = status == .playing
|
||||
}
|
||||
return controller.view
|
||||
}
|
||||
|
||||
@@ -50,12 +59,13 @@ struct VideoPlayerView: UIViewRepresentable {
|
||||
class Coordinator: NSObject {
|
||||
var controller: AVPlayerViewController?
|
||||
var timeObserver: Any? = nil
|
||||
var publisher: AnyCancellable? = nil
|
||||
|
||||
deinit {
|
||||
print("deinit coordinator of VideoPlayer")
|
||||
if let timeObserver = timeObserver {
|
||||
NotificationCenter.default.removeObserver(timeObserver)
|
||||
}
|
||||
publisher?.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ struct AddContactView: View {
|
||||
let conn = try await apiSetConnectionIncognito(connId: contactConn.pccConnId, incognito: incognito) {
|
||||
await MainActor.run {
|
||||
contactConnection = conn
|
||||
ChatModel.shared.updateContactConnection(conn)
|
||||
chatModel.updateContactConnection(conn)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
|
||||
@@ -12,6 +12,7 @@ import SimpleXChat
|
||||
struct AddGroupView: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@Environment(\.dismiss) var dismiss: DismissAction
|
||||
@AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false
|
||||
@State private var chat: Chat?
|
||||
@State private var groupInfo: GroupInfo?
|
||||
@State private var profile = GroupProfile(displayName: "", fullName: "")
|
||||
@@ -21,18 +22,35 @@ struct AddGroupView: View {
|
||||
@State private var showTakePhoto = false
|
||||
@State private var chosenImage: UIImage? = nil
|
||||
@State private var showInvalidNameAlert = false
|
||||
@State private var groupLink: String?
|
||||
@State private var groupLinkMemberRole: GroupMemberRole = .member
|
||||
|
||||
var body: some View {
|
||||
if let chat = chat, let groupInfo = groupInfo {
|
||||
AddGroupMembersViewCommon(
|
||||
chat: chat,
|
||||
groupInfo: groupInfo,
|
||||
creatingGroup: true,
|
||||
showFooterCounter: false
|
||||
) { _ in
|
||||
dismiss()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
m.chatId = groupInfo.id
|
||||
if !groupInfo.membership.memberIncognito {
|
||||
AddGroupMembersViewCommon(
|
||||
chat: chat,
|
||||
groupInfo: groupInfo,
|
||||
creatingGroup: true,
|
||||
showFooterCounter: false
|
||||
) { _ in
|
||||
dismiss()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
m.chatId = groupInfo.id
|
||||
}
|
||||
}
|
||||
} else {
|
||||
GroupLinkView(
|
||||
groupId: groupInfo.groupId,
|
||||
groupLink: $groupLink,
|
||||
groupLinkMemberRole: $groupLinkMemberRole,
|
||||
showTitle: true,
|
||||
creatingGroup: true
|
||||
) {
|
||||
dismiss()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
m.chatId = groupInfo.id
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -41,77 +59,62 @@ struct AddGroupView: View {
|
||||
}
|
||||
|
||||
func createGroupView() -> some View {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Create secret group")
|
||||
.font(.largeTitle)
|
||||
.padding(.vertical, 4)
|
||||
Text("The group is fully decentralized – it is visible only to the members.")
|
||||
.padding(.bottom, 4)
|
||||
List {
|
||||
Group {
|
||||
Text("Create secret group")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(.bottom, 24)
|
||||
.onTapGesture(perform: hideKeyboard)
|
||||
|
||||
HStack {
|
||||
Image(systemName: "info.circle").foregroundColor(.secondary).font(.footnote)
|
||||
Spacer().frame(width: 8)
|
||||
Text("Your chat profile will be sent to group members").font(.footnote)
|
||||
}
|
||||
.padding(.bottom)
|
||||
|
||||
ZStack(alignment: .center) {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
profileImageView(profile.image)
|
||||
if profile.image != nil {
|
||||
Button {
|
||||
profile.image = nil
|
||||
} label: {
|
||||
Image(systemName: "multiply")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 12)
|
||||
ZStack(alignment: .center) {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
ProfileImage(imageStr: profile.image, color: Color(uiColor: .secondarySystemGroupedBackground))
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.frame(maxWidth: 128, maxHeight: 128)
|
||||
if profile.image != nil {
|
||||
Button {
|
||||
profile.image = nil
|
||||
} label: {
|
||||
Image(systemName: "multiply")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 12)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
editImageButton { showChooseSource = true }
|
||||
.buttonStyle(BorderlessButtonStyle()) // otherwise whole "list row" is clickable
|
||||
}
|
||||
|
||||
editImageButton { showChooseSource = true }
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
.padding(.bottom, 4)
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||
|
||||
ZStack(alignment: .topLeading) {
|
||||
let name = profile.displayName.trimmingCharacters(in: .whitespaces)
|
||||
if name != mkValidName(name) {
|
||||
Button {
|
||||
showInvalidNameAlert = true
|
||||
} label: {
|
||||
Image(systemName: "exclamationmark.circle").foregroundColor(.red)
|
||||
}
|
||||
} else {
|
||||
Image(systemName: "exclamationmark.circle").foregroundColor(.clear)
|
||||
Section {
|
||||
groupNameTextField()
|
||||
Button(action: createGroup) {
|
||||
settingsRow("checkmark", color: .accentColor) { Text("Create group") }
|
||||
}
|
||||
textField("Enter group name…", text: $profile.displayName)
|
||||
.focused($focusDisplayName)
|
||||
.submitLabel(.go)
|
||||
.onSubmit {
|
||||
if canCreateProfile() { createGroup() }
|
||||
}
|
||||
.disabled(!canCreateProfile())
|
||||
IncognitoToggle(incognitoEnabled: $incognitoDefault)
|
||||
} footer: {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
sharedGroupProfileInfo(incognitoDefault)
|
||||
Text("Fully decentralized – visible only to members.")
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.onTapGesture(perform: hideKeyboard)
|
||||
}
|
||||
.padding(.bottom)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
createGroup()
|
||||
} label: {
|
||||
Text("Create")
|
||||
Image(systemName: "greaterthan")
|
||||
}
|
||||
.disabled(!canCreateProfile())
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
}
|
||||
.onAppear() {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
focusDisplayName = true
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.confirmationDialog("Group image", isPresented: $showChooseSource, titleVisibility: .visible) {
|
||||
Button("Take picture") {
|
||||
showTakePhoto = true
|
||||
@@ -141,24 +144,52 @@ struct AddGroupView: View {
|
||||
profile.image = nil
|
||||
}
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture { hideKeyboard() }
|
||||
}
|
||||
|
||||
func groupNameTextField() -> some View {
|
||||
ZStack(alignment: .leading) {
|
||||
let name = profile.displayName.trimmingCharacters(in: .whitespaces)
|
||||
if name != mkValidName(name) {
|
||||
Button {
|
||||
showInvalidNameAlert = true
|
||||
} label: {
|
||||
Image(systemName: "exclamationmark.circle").foregroundColor(.red)
|
||||
}
|
||||
} else {
|
||||
Image(systemName: "pencil").foregroundColor(.secondary)
|
||||
}
|
||||
textField("Enter group name…", text: $profile.displayName)
|
||||
.focused($focusDisplayName)
|
||||
.submitLabel(.continue)
|
||||
.onSubmit {
|
||||
if canCreateProfile() { createGroup() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func textField(_ placeholder: LocalizedStringKey, text: Binding<String>) -> some View {
|
||||
TextField(placeholder, text: text)
|
||||
.padding(.leading, 32)
|
||||
.padding(.leading, 36)
|
||||
}
|
||||
|
||||
func sharedGroupProfileInfo(_ 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 createGroup() {
|
||||
hideKeyboard()
|
||||
do {
|
||||
profile.displayName = profile.displayName.trimmingCharacters(in: .whitespaces)
|
||||
let gInfo = try apiNewGroup(profile)
|
||||
let gInfo = try apiNewGroup(incognito: incognitoDefault, groupProfile: profile)
|
||||
Task {
|
||||
let groupMembers = await apiListMembers(gInfo.groupId)
|
||||
await MainActor.run {
|
||||
ChatModel.shared.groupMembers = groupMembers
|
||||
m.groupMembers = groupMembers.map { GMember.init($0) }
|
||||
}
|
||||
}
|
||||
let c = Chat(chatInfo: .group(groupInfo: gInfo), chatItems: [])
|
||||
|
||||
@@ -155,12 +155,14 @@ func planAndConnectAlert(_ alert: PlanAndConnectAlert, dismiss: Bool) -> Alert {
|
||||
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)"
|
||||
}
|
||||
}
|
||||
@@ -186,6 +188,15 @@ func planAndConnectActionSheet(_ sheet: PlanAndConnectActionSheet, dismiss: Bool
|
||||
.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(
|
||||
@@ -277,6 +288,13 @@ func planAndConnect(
|
||||
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 {
|
||||
@@ -315,6 +333,17 @@ func planAndConnect(
|
||||
}
|
||||
}
|
||||
|
||||
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 = await apiConnect(incognito: incognito, connReq: connectionLink) {
|
||||
|
||||
@@ -54,9 +54,11 @@ struct PasteToConnectView: View {
|
||||
|
||||
IncognitoToggle(incognitoEnabled: $incognitoDefault)
|
||||
} footer: {
|
||||
sharedProfileInfo(incognitoDefault)
|
||||
+ Text(String("\n\n"))
|
||||
+ Text("You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button.")
|
||||
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) }
|
||||
|
||||
@@ -38,11 +38,11 @@ struct ScanToConnectView: View {
|
||||
)
|
||||
.padding(.top)
|
||||
|
||||
Group {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
sharedProfileInfo(incognitoDefault)
|
||||
+ Text(String("\n\n"))
|
||||
+ Text("If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link.")
|
||||
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)
|
||||
|
||||
@@ -283,6 +283,37 @@ private let versionDescriptions: [VersionDescription] = [
|
||||
),
|
||||
]
|
||||
),
|
||||
VersionDescription(
|
||||
version: "v5.4",
|
||||
post: URL(string: "https://simplex.chat/blog/20231125-simplex-chat-v5-4-link-mobile-desktop-quantum-resistant-better-groups.html"),
|
||||
features: [
|
||||
FeatureDescription(
|
||||
icon: "desktopcomputer",
|
||||
title: "Link mobile and desktop apps! 🔗",
|
||||
description: "Via secure quantum resistant protocol."
|
||||
),
|
||||
FeatureDescription(
|
||||
icon: "person.2",
|
||||
title: "Better groups",
|
||||
description: "Faster joining and more reliable messages."
|
||||
),
|
||||
FeatureDescription(
|
||||
icon: "theatermasks",
|
||||
title: "Incognito groups",
|
||||
description: "Create a group using a random profile."
|
||||
),
|
||||
FeatureDescription(
|
||||
icon: "hand.raised",
|
||||
title: "Block group members",
|
||||
description: "To hide unwanted messages."
|
||||
),
|
||||
FeatureDescription(
|
||||
icon: "gift",
|
||||
title: "A few more things",
|
||||
description: "- optionally notify deleted contacts.\n- profile names with spaces.\n- and more!"
|
||||
),
|
||||
]
|
||||
),
|
||||
]
|
||||
|
||||
private let lastVersion = versionDescriptions.last!.version
|
||||
|
||||
556
apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift
Normal file
556
apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift
Normal file
@@ -0,0 +1,556 @@
|
||||
//
|
||||
// ConnectDesktopView.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by Evgeny on 13/10/2023.
|
||||
// Copyright © 2023 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
import CodeScanner
|
||||
|
||||
struct ConnectDesktopView: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@Environment(\.dismiss) var dismiss: DismissAction
|
||||
var viaSettings = false
|
||||
@AppStorage(DEFAULT_DEVICE_NAME_FOR_REMOTE_ACCESS) private var deviceName = UIDevice.current.name
|
||||
@AppStorage(DEFAULT_CONFIRM_REMOTE_SESSIONS) private var confirmRemoteSessions = false
|
||||
@AppStorage(DEFAULT_CONNECT_REMOTE_VIA_MULTICAST) private var connectRemoteViaMulticast = true
|
||||
@AppStorage(DEFAULT_CONNECT_REMOTE_VIA_MULTICAST_AUTO) private var connectRemoteViaMulticastAuto = true
|
||||
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
|
||||
@State private var sessionAddress: String = ""
|
||||
@State private var remoteCtrls: [RemoteCtrlInfo] = []
|
||||
@State private var alert: ConnectDesktopAlert?
|
||||
@State private var showConnectScreen = true
|
||||
@State private var showQRCodeScanner = true
|
||||
@State private var firstAppearance = true
|
||||
|
||||
private var useMulticast: Bool {
|
||||
connectRemoteViaMulticast && !remoteCtrls.isEmpty
|
||||
}
|
||||
|
||||
private enum ConnectDesktopAlert: Identifiable {
|
||||
case unlinkDesktop(rc: RemoteCtrlInfo)
|
||||
case disconnectDesktop(action: UserDisconnectAction)
|
||||
case badInvitationError
|
||||
case badVersionError(version: String?)
|
||||
case desktopDisconnectedError
|
||||
case error(title: LocalizedStringKey, error: LocalizedStringKey = "")
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case let .unlinkDesktop(rc): "unlinkDesktop \(rc.remoteCtrlId)"
|
||||
case let .disconnectDesktop(action): "disconnectDecktop \(action)"
|
||||
case .badInvitationError: "badInvitationError"
|
||||
case let .badVersionError(v): "badVersionError \(v ?? "")"
|
||||
case .desktopDisconnectedError: "desktopDisconnectedError"
|
||||
case let .error(title, _): "error \(title)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum UserDisconnectAction: String {
|
||||
case back
|
||||
case dismiss // TODO dismiss settings after confirmation
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if viaSettings {
|
||||
viewBody
|
||||
.modifier(BackButton(label: "Back") {
|
||||
if m.activeRemoteCtrl {
|
||||
alert = .disconnectDesktop(action: .back)
|
||||
} else {
|
||||
dismiss()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
NavigationView {
|
||||
viewBody
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var viewBody: some View {
|
||||
Group {
|
||||
let discovery = m.remoteCtrlSession?.discovery
|
||||
if discovery == true || (discovery == nil && !showConnectScreen) {
|
||||
searchingDesktopView()
|
||||
} else if let session = m.remoteCtrlSession {
|
||||
switch session.sessionState {
|
||||
case .starting: connectingDesktopView(session, nil)
|
||||
case .searching: searchingDesktopView()
|
||||
case let .found(rc, compatible): foundDesktopView(session, rc, compatible)
|
||||
case let .connecting(rc_): connectingDesktopView(session, rc_)
|
||||
case let .pendingConfirmation(rc_, sessCode):
|
||||
if confirmRemoteSessions || rc_ == nil {
|
||||
verifySessionView(session, rc_, sessCode)
|
||||
} else {
|
||||
connectingDesktopView(session, rc_).onAppear {
|
||||
verifyDesktopSessionCode(sessCode)
|
||||
}
|
||||
}
|
||||
case let .connected(rc, _): activeSessionView(session, rc)
|
||||
}
|
||||
// The hack below prevents camera freezing when exiting linked devices view.
|
||||
// Using showQRCodeScanner inside connectDesktopView or passing it as parameter still results in freezing.
|
||||
} else if showQRCodeScanner || firstAppearance {
|
||||
connectDesktopView()
|
||||
} else {
|
||||
connectDesktopView(showScanner: false)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
setDeviceName(deviceName)
|
||||
updateRemoteCtrls()
|
||||
showConnectScreen = !useMulticast
|
||||
if m.remoteCtrlSession != nil {
|
||||
disconnectDesktop()
|
||||
} else if useMulticast {
|
||||
findKnownDesktop()
|
||||
}
|
||||
// The hack below prevents camera freezing when exiting linked devices view.
|
||||
// `firstAppearance` prevents camera flicker when the view first opens.
|
||||
// moving `showQRCodeScanner = false` to `onDisappear` (to avoid `firstAppearance`) does not prevent freeze.
|
||||
showQRCodeScanner = false
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
|
||||
firstAppearance = false
|
||||
showQRCodeScanner = true
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
if m.remoteCtrlSession != nil {
|
||||
showConnectScreen = false
|
||||
disconnectDesktop()
|
||||
}
|
||||
}
|
||||
.onChange(of: deviceName) {
|
||||
setDeviceName($0)
|
||||
}
|
||||
.onChange(of: m.activeRemoteCtrl) {
|
||||
UIApplication.shared.isIdleTimerDisabled = $0
|
||||
}
|
||||
.alert(item: $alert) { a in
|
||||
switch a {
|
||||
case let .unlinkDesktop(rc):
|
||||
Alert(
|
||||
title: Text("Unlink desktop?"),
|
||||
primaryButton: .destructive(Text("Unlink")) {
|
||||
unlinkDesktop(rc)
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
case let .disconnectDesktop(action):
|
||||
Alert(
|
||||
title: Text("Disconnect desktop?"),
|
||||
primaryButton: .destructive(Text("Disconnect")) {
|
||||
disconnectDesktop(action)
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
case .badInvitationError:
|
||||
Alert(title: Text("Bad desktop address"))
|
||||
case let .badVersionError(v):
|
||||
Alert(
|
||||
title: Text("Incompatible version"),
|
||||
message: Text("Desktop app version \(v ?? "") is not compatible with this app.")
|
||||
)
|
||||
case .desktopDisconnectedError:
|
||||
Alert(title: Text("Connection terminated"))
|
||||
case let .error(title, error):
|
||||
Alert(title: Text(title), message: Text(error))
|
||||
}
|
||||
}
|
||||
.interactiveDismissDisabled(m.activeRemoteCtrl)
|
||||
}
|
||||
|
||||
private func connectDesktopView(showScanner: Bool = true) -> some View {
|
||||
List {
|
||||
Section("This device name") {
|
||||
devicesView()
|
||||
}
|
||||
if showScanner {
|
||||
scanDesctopAddressView()
|
||||
}
|
||||
if developerTools {
|
||||
desktopAddressView()
|
||||
}
|
||||
}
|
||||
.navigationTitle("Connect to desktop")
|
||||
}
|
||||
|
||||
private func connectingDesktopView(_ session: RemoteCtrlSession, _ rc: RemoteCtrlInfo?) -> some View {
|
||||
List {
|
||||
Section("Connecting to desktop") {
|
||||
ctrlDeviceNameText(session, rc)
|
||||
ctrlDeviceVersionText(session)
|
||||
}
|
||||
|
||||
if let sessCode = session.sessionCode {
|
||||
Section("Session code") {
|
||||
sessionCodeText(sessCode)
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
disconnectButton()
|
||||
}
|
||||
}
|
||||
.navigationTitle("Connecting to desktop")
|
||||
}
|
||||
|
||||
private func searchingDesktopView() -> some View {
|
||||
List {
|
||||
Section("This device name") {
|
||||
devicesView()
|
||||
}
|
||||
Section("Found desktop") {
|
||||
Text("Waiting for desktop...").italic()
|
||||
Button {
|
||||
disconnectDesktop(.dismiss)
|
||||
} label: {
|
||||
Label("Scan QR code", systemImage: "qrcode")
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Connecting to desktop")
|
||||
}
|
||||
|
||||
@ViewBuilder private func foundDesktopView(_ session: RemoteCtrlSession, _ rc: RemoteCtrlInfo, _ compatible: Bool) -> some View {
|
||||
let v = List {
|
||||
Section("This device name") {
|
||||
devicesView()
|
||||
}
|
||||
Section("Found desktop") {
|
||||
ctrlDeviceNameText(session, rc)
|
||||
ctrlDeviceVersionText(session)
|
||||
if !compatible {
|
||||
Text("Not compatible!").foregroundColor(.red)
|
||||
} else if !connectRemoteViaMulticastAuto {
|
||||
Button {
|
||||
confirmKnownDesktop(rc)
|
||||
} label: {
|
||||
Label("Connect", systemImage: "checkmark")
|
||||
}
|
||||
}
|
||||
}
|
||||
if !compatible && !connectRemoteViaMulticastAuto {
|
||||
Section {
|
||||
disconnectButton("Cancel")
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Found desktop")
|
||||
|
||||
if compatible && connectRemoteViaMulticastAuto {
|
||||
v.onAppear { confirmKnownDesktop(rc) }
|
||||
} else {
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
private func verifySessionView(_ session: RemoteCtrlSession, _ rc: RemoteCtrlInfo?, _ sessCode: String) -> some View {
|
||||
List {
|
||||
Section("Connected to desktop") {
|
||||
ctrlDeviceNameText(session, rc)
|
||||
ctrlDeviceVersionText(session)
|
||||
}
|
||||
|
||||
Section("Verify code with desktop") {
|
||||
sessionCodeText(sessCode)
|
||||
Button {
|
||||
verifyDesktopSessionCode(sessCode)
|
||||
} label: {
|
||||
Label("Confirm", systemImage: "checkmark")
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
disconnectButton()
|
||||
}
|
||||
}
|
||||
.navigationTitle("Verify connection")
|
||||
}
|
||||
|
||||
private func ctrlDeviceNameText(_ session: RemoteCtrlSession, _ rc: RemoteCtrlInfo?) -> Text {
|
||||
var t = Text(rc?.deviceViewName ?? session.ctrlAppInfo?.deviceName ?? "")
|
||||
if (rc == nil) {
|
||||
t = t + Text(" ") + Text("(new)").italic()
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
private func ctrlDeviceVersionText(_ session: RemoteCtrlSession) -> Text {
|
||||
let v = session.ctrlAppInfo?.appVersionRange.maxVersion
|
||||
var t = Text("v\(v ?? "")")
|
||||
if v != session.appVersion {
|
||||
t = t + Text(" ") + Text("(this device v\(session.appVersion))").italic()
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
private func activeSessionView(_ session: RemoteCtrlSession, _ rc: RemoteCtrlInfo) -> some View {
|
||||
List {
|
||||
Section("Connected desktop") {
|
||||
Text(rc.deviceViewName)
|
||||
ctrlDeviceVersionText(session)
|
||||
}
|
||||
|
||||
if let sessCode = session.sessionCode {
|
||||
Section("Session code") {
|
||||
sessionCodeText(sessCode)
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
disconnectButton()
|
||||
} footer: {
|
||||
// This is specific to iOS
|
||||
Text("Keep the app open to use it from desktop")
|
||||
}
|
||||
}
|
||||
.navigationTitle("Connected to desktop")
|
||||
}
|
||||
|
||||
private func sessionCodeText(_ code: String) -> some View {
|
||||
Text(code.prefix(23))
|
||||
}
|
||||
|
||||
private func devicesView() -> some View {
|
||||
Group {
|
||||
TextField("Enter this device name…", text: $deviceName)
|
||||
if !remoteCtrls.isEmpty {
|
||||
NavigationLink {
|
||||
linkedDesktopsView()
|
||||
} label: {
|
||||
Text("Linked desktops")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func scanDesctopAddressView() -> some View {
|
||||
Section("Scan QR code from desktop") {
|
||||
CodeScannerView(codeTypes: [.qr], completion: processDesktopQRCode)
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.cornerRadius(12)
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
|
||||
private func desktopAddressView() -> some View {
|
||||
Section("Desktop address") {
|
||||
if sessionAddress.isEmpty {
|
||||
Button {
|
||||
sessionAddress = UIPasteboard.general.string ?? ""
|
||||
} label: {
|
||||
Label("Paste desktop address", systemImage: "doc.plaintext")
|
||||
}
|
||||
.disabled(!UIPasteboard.general.hasStrings)
|
||||
} else {
|
||||
HStack {
|
||||
Text(sessionAddress).lineLimit(1)
|
||||
Spacer()
|
||||
Image(systemName: "multiply.circle.fill")
|
||||
.foregroundColor(.secondary)
|
||||
.onTapGesture { sessionAddress = "" }
|
||||
}
|
||||
}
|
||||
Button {
|
||||
connectDesktopAddress(sessionAddress)
|
||||
} label: {
|
||||
Label("Connect to desktop", systemImage: "rectangle.connected.to.line.below")
|
||||
}
|
||||
.disabled(sessionAddress.isEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
private func linkedDesktopsView() -> some View {
|
||||
List {
|
||||
Section("Desktop devices") {
|
||||
ForEach(remoteCtrls, id: \.remoteCtrlId) { rc in
|
||||
remoteCtrlView(rc)
|
||||
}
|
||||
.onDelete { indexSet in
|
||||
if let i = indexSet.first, i < remoteCtrls.count {
|
||||
alert = .unlinkDesktop(rc: remoteCtrls[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section("Linked desktop options") {
|
||||
Toggle("Verify connections", isOn: $confirmRemoteSessions)
|
||||
Toggle("Discover via local network", isOn: $connectRemoteViaMulticast)
|
||||
if connectRemoteViaMulticast {
|
||||
Toggle("Connect automatically", isOn: $connectRemoteViaMulticastAuto)
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Linked desktops")
|
||||
}
|
||||
|
||||
private func remoteCtrlView(_ rc: RemoteCtrlInfo) -> some View {
|
||||
Text(rc.deviceViewName)
|
||||
}
|
||||
|
||||
|
||||
private func setDeviceName(_ name: String) {
|
||||
do {
|
||||
try setLocalDeviceName(deviceName)
|
||||
} catch let e {
|
||||
errorAlert(e)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateRemoteCtrls() {
|
||||
do {
|
||||
remoteCtrls = try listRemoteCtrls()
|
||||
} catch let e {
|
||||
errorAlert(e)
|
||||
}
|
||||
}
|
||||
|
||||
private func processDesktopQRCode(_ resp: Result<ScanResult, ScanError>) {
|
||||
switch resp {
|
||||
case let .success(r): connectDesktopAddress(r.string)
|
||||
case let .failure(e): errorAlert(e)
|
||||
}
|
||||
}
|
||||
|
||||
private func findKnownDesktop() {
|
||||
Task {
|
||||
do {
|
||||
try await findKnownRemoteCtrl()
|
||||
await MainActor.run {
|
||||
m.remoteCtrlSession = RemoteCtrlSession(
|
||||
ctrlAppInfo: nil,
|
||||
appVersion: "",
|
||||
sessionState: .searching
|
||||
)
|
||||
showConnectScreen = true
|
||||
}
|
||||
} catch let e {
|
||||
await MainActor.run {
|
||||
errorAlert(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func confirmKnownDesktop(_ rc: RemoteCtrlInfo) {
|
||||
connectDesktop_ {
|
||||
try await confirmRemoteCtrl(rc.remoteCtrlId)
|
||||
}
|
||||
}
|
||||
|
||||
private func connectDesktopAddress(_ addr: String) {
|
||||
connectDesktop_ {
|
||||
try await connectRemoteCtrl(desktopAddress: addr)
|
||||
}
|
||||
}
|
||||
|
||||
private func connectDesktop_(_ connect: @escaping () async throws -> (RemoteCtrlInfo?, CtrlAppInfo, String)) {
|
||||
Task {
|
||||
do {
|
||||
let (rc_, ctrlAppInfo, v) = try await connect()
|
||||
await MainActor.run {
|
||||
sessionAddress = ""
|
||||
m.remoteCtrlSession = RemoteCtrlSession(
|
||||
ctrlAppInfo: ctrlAppInfo,
|
||||
appVersion: v,
|
||||
sessionState: .connecting(remoteCtrl_: rc_)
|
||||
)
|
||||
}
|
||||
} catch let e {
|
||||
await MainActor.run {
|
||||
switch e as? ChatResponse {
|
||||
case .chatCmdError(_, .errorRemoteCtrl(.badInvitation)): alert = .badInvitationError
|
||||
case .chatCmdError(_, .error(.commandError)): alert = .badInvitationError
|
||||
case let .chatCmdError(_, .errorRemoteCtrl(.badVersion(v))): alert = .badVersionError(version: v)
|
||||
case .chatCmdError(_, .errorAgent(.RCP(.version))): alert = .badVersionError(version: nil)
|
||||
case .chatCmdError(_, .errorAgent(.RCP(.ctrlAuth))): alert = .desktopDisconnectedError
|
||||
default: errorAlert(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func verifyDesktopSessionCode(_ sessCode: String) {
|
||||
Task {
|
||||
do {
|
||||
let rc = try await verifyRemoteCtrlSession(sessCode)
|
||||
await MainActor.run {
|
||||
m.remoteCtrlSession = m.remoteCtrlSession?.updateState(.connected(remoteCtrl: rc, sessionCode: sessCode))
|
||||
}
|
||||
await MainActor.run {
|
||||
updateRemoteCtrls()
|
||||
}
|
||||
} catch let error {
|
||||
await MainActor.run {
|
||||
errorAlert(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func disconnectButton(_ label: LocalizedStringKey = "Disconnect") -> some View {
|
||||
Button {
|
||||
disconnectDesktop(.dismiss)
|
||||
} label: {
|
||||
Label(label, systemImage: "multiply")
|
||||
}
|
||||
}
|
||||
|
||||
private func disconnectDesktop(_ action: UserDisconnectAction? = nil) {
|
||||
Task {
|
||||
do {
|
||||
try await stopRemoteCtrl()
|
||||
await MainActor.run {
|
||||
if case .connected = m.remoteCtrlSession?.sessionState {
|
||||
switchToLocalSession()
|
||||
} else {
|
||||
m.remoteCtrlSession = nil
|
||||
}
|
||||
switch action {
|
||||
case .back: dismiss()
|
||||
case .dismiss: dismiss()
|
||||
case .none: ()
|
||||
}
|
||||
}
|
||||
} catch let e {
|
||||
await MainActor.run {
|
||||
errorAlert(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func unlinkDesktop(_ rc: RemoteCtrlInfo) {
|
||||
Task {
|
||||
do {
|
||||
try await deleteRemoteCtrl(rc.remoteCtrlId)
|
||||
await MainActor.run {
|
||||
remoteCtrls.removeAll(where: { $0.remoteCtrlId == rc.remoteCtrlId })
|
||||
}
|
||||
} catch let e {
|
||||
await MainActor.run {
|
||||
errorAlert(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func errorAlert(_ error: Error) {
|
||||
let a = getErrorAlert(error, "Error")
|
||||
alert = .error(title: a.title, error: a.message)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ConnectDesktopView()
|
||||
}
|
||||
@@ -119,7 +119,7 @@ struct PrivacySettings: View {
|
||||
Text("Send delivery receipts to")
|
||||
} footer: {
|
||||
VStack(alignment: .leading) {
|
||||
Text("These settings are for your current profile **\(ChatModel.shared.currentUser?.displayName ?? "")**.")
|
||||
Text("These settings are for your current profile **\(m.currentUser?.displayName ?? "")**.")
|
||||
Text("They can be overridden in contact and group settings.")
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
@@ -53,6 +53,10 @@ let DEFAULT_WHATS_NEW_VERSION = "defaultWhatsNewVersion"
|
||||
let DEFAULT_ONBOARDING_STAGE = "onboardingStage"
|
||||
let DEFAULT_CUSTOM_DISAPPEARING_MESSAGE_TIME = "customDisappearingMessageTime"
|
||||
let DEFAULT_SHOW_UNREAD_AND_FAVORITES = "showUnreadAndFavorites"
|
||||
let DEFAULT_DEVICE_NAME_FOR_REMOTE_ACCESS = "deviceNameForRemoteAccess"
|
||||
let DEFAULT_CONFIRM_REMOTE_SESSIONS = "confirmRemoteSessions"
|
||||
let DEFAULT_CONNECT_REMOTE_VIA_MULTICAST = "connectRemoteViaMulticast"
|
||||
let DEFAULT_CONNECT_REMOTE_VIA_MULTICAST_AUTO = "connectRemoteViaMulticastAuto"
|
||||
|
||||
let appDefaults: [String: Any] = [
|
||||
DEFAULT_SHOW_LA_NOTICE: false,
|
||||
@@ -85,7 +89,10 @@ let appDefaults: [String: Any] = [
|
||||
DEFAULT_SHOW_MUTE_PROFILE_ALERT: true,
|
||||
DEFAULT_ONBOARDING_STAGE: OnboardingStage.onboardingComplete.rawValue,
|
||||
DEFAULT_CUSTOM_DISAPPEARING_MESSAGE_TIME: 300,
|
||||
DEFAULT_SHOW_UNREAD_AND_FAVORITES: false
|
||||
DEFAULT_SHOW_UNREAD_AND_FAVORITES: false,
|
||||
DEFAULT_CONFIRM_REMOTE_SESSIONS: false,
|
||||
DEFAULT_CONNECT_REMOTE_VIA_MULTICAST: true,
|
||||
DEFAULT_CONNECT_REMOTE_VIA_MULTICAST_AUTO: true,
|
||||
]
|
||||
|
||||
enum SimpleXLinkMode: String, Identifiable {
|
||||
@@ -178,6 +185,12 @@ struct SettingsView: View {
|
||||
} label: {
|
||||
settingsRow("switch.2") { Text("Chat preferences") }
|
||||
}
|
||||
|
||||
NavigationLink {
|
||||
ConnectDesktopView(viaSettings: true)
|
||||
} label: {
|
||||
settingsRow("desktopcomputer") { Text("Use from desktop") }
|
||||
}
|
||||
}
|
||||
.disabled(chatModel.chatRunning != true)
|
||||
|
||||
@@ -362,7 +375,9 @@ struct SettingsView: View {
|
||||
|
||||
func settingsRow<Content : View>(_ icon: String, color: Color = .secondary, content: @escaping () -> Content) -> some View {
|
||||
ZStack(alignment: .leading) {
|
||||
Image(systemName: icon).frame(maxWidth: 24, maxHeight: 24, alignment: .center).foregroundColor(color)
|
||||
Image(systemName: icon).frame(maxWidth: 24, maxHeight: 24, alignment: .center)
|
||||
.symbolRenderingMode(.monochrome)
|
||||
.foregroundColor(color)
|
||||
content().padding(.leading, indent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,11 +174,13 @@ struct UserProfile: View {
|
||||
chatModel.updateCurrentUser(newProfile)
|
||||
profile = newProfile
|
||||
}
|
||||
editProfile = false
|
||||
} else {
|
||||
alert = .duplicateUserError
|
||||
}
|
||||
} catch {
|
||||
logger.error("UserProfile apiUpdateProfile error: \(responseError(error))")
|
||||
}
|
||||
editProfile = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,5 +18,9 @@
|
||||
<array>
|
||||
<string>$(AppIdentifierPrefix)chat.simplex.app</string>
|
||||
</array>
|
||||
<key>com.apple.developer.networking.multicast</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.device-information.user-assigned-device-name</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -87,6 +87,10 @@
|
||||
<target>%@ / %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ and %@" xml:space="preserve">
|
||||
<source>%@ and %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ and %@ connected" xml:space="preserve">
|
||||
<source>%@ and %@ connected</source>
|
||||
<target>%@ и %@ са свързани</target>
|
||||
@@ -97,6 +101,10 @@
|
||||
<target>%1$@ в %2$@:</target>
|
||||
<note>copied message info, <sender> at <time></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ connected" xml:space="preserve">
|
||||
<source>%@ connected</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ is connected!" xml:space="preserve">
|
||||
<source>%@ is connected!</source>
|
||||
<target>%@ е свързан!</target>
|
||||
@@ -122,6 +130,10 @@
|
||||
<target>%@ иска да се свърже!</target>
|
||||
<note>notification title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@, %@ and %lld members" xml:space="preserve">
|
||||
<source>%@, %@ and %lld members</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@, %@ and %lld other members connected" xml:space="preserve">
|
||||
<source>%@, %@ and %lld other members connected</source>
|
||||
<target>%@, %@ и %lld други членове са свързани</target>
|
||||
@@ -187,11 +199,27 @@
|
||||
<target>%lld файл(а) с общ размер от %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld group events" xml:space="preserve">
|
||||
<source>%lld group events</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld members" xml:space="preserve">
|
||||
<source>%lld members</source>
|
||||
<target>%lld членове</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld messages blocked" xml:space="preserve">
|
||||
<source>%lld messages blocked</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>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld messages moderated by %@" xml:space="preserve">
|
||||
<source>%lld messages moderated by %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld minutes" xml:space="preserve">
|
||||
<source>%lld minutes</source>
|
||||
<target>%lld минути</target>
|
||||
@@ -262,6 +290,14 @@
|
||||
<target>(</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="(new)" xml:space="preserve">
|
||||
<source>(new)</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="(this device v%@)" xml:space="preserve">
|
||||
<source>(this device v%@)</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id=")" xml:space="preserve">
|
||||
<source>)</source>
|
||||
<target>)</target>
|
||||
@@ -350,6 +386,12 @@
|
||||
- и още!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="- optionally notify deleted contacts. - profile names with spaces. - and more!" xml:space="preserve">
|
||||
<source>- optionally notify deleted contacts.
|
||||
- profile names with spaces.
|
||||
- and more!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="- voice messages up to 5 minutes. - custom time to disappear. - editing history." xml:space="preserve">
|
||||
<source>- voice messages up to 5 minutes.
|
||||
- custom time to disappear.
|
||||
@@ -364,6 +406,10 @@
|
||||
<target>.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="0 sec" xml:space="preserve">
|
||||
<source>0 sec</source>
|
||||
<note>time to disappear</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="0s" xml:space="preserve">
|
||||
<source>0s</source>
|
||||
<target>0s</target>
|
||||
@@ -589,6 +635,10 @@
|
||||
<target>Всички съобщения ще бъдат изтрити - това не може да бъде отменено! Съобщенията ще бъдат изтрити САМО за вас.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All new messages from %@ will be hidden!" xml:space="preserve">
|
||||
<source>All new messages from %@ will be hidden!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All your contacts will remain connected." xml:space="preserve">
|
||||
<source>All your contacts will remain connected.</source>
|
||||
<target>Всички ваши контакти ще останат свързани.</target>
|
||||
@@ -694,6 +744,14 @@
|
||||
<target>Вече сте свързани?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Already connecting!" xml:space="preserve">
|
||||
<source>Already connecting!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Already joining the group!" xml:space="preserve">
|
||||
<source>Already joining the group!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Always use relay" xml:space="preserve">
|
||||
<source>Always use relay</source>
|
||||
<target>Винаги използвай реле</target>
|
||||
@@ -814,6 +872,10 @@
|
||||
<target>Назад</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Bad desktop address" xml:space="preserve">
|
||||
<source>Bad desktop address</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Bad message ID" xml:space="preserve">
|
||||
<source>Bad message ID</source>
|
||||
<target>Лошо ID на съобщението</target>
|
||||
@@ -824,11 +886,31 @@
|
||||
<target>Лош хеш на съобщението</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better groups" xml:space="preserve">
|
||||
<source>Better groups</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better messages" xml:space="preserve">
|
||||
<source>Better messages</source>
|
||||
<target>По-добри съобщения</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Block" xml:space="preserve">
|
||||
<source>Block</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>
|
||||
</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="Block member?" xml:space="preserve">
|
||||
<source>Block member?</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>
|
||||
@@ -1090,9 +1172,8 @@
|
||||
<target>Свързване</target>
|
||||
<note>server test step</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect directly" xml:space="preserve">
|
||||
<source>Connect directly</source>
|
||||
<target>Свързване директно</target>
|
||||
<trans-unit id="Connect automatically" xml:space="preserve">
|
||||
<source>Connect automatically</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect incognito" xml:space="preserve">
|
||||
@@ -1100,14 +1181,26 @@
|
||||
<target>Свързване инкогнито</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via contact link" xml:space="preserve">
|
||||
<source>Connect via contact link</source>
|
||||
<target>Свързване чрез линк на контакта</target>
|
||||
<trans-unit id="Connect to desktop" xml:space="preserve">
|
||||
<source>Connect to desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via group link?" xml:space="preserve">
|
||||
<source>Connect via group link?</source>
|
||||
<target>Свързване чрез групов линк?</target>
|
||||
<trans-unit id="Connect to yourself?" xml:space="preserve">
|
||||
<source>Connect to yourself?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect to yourself? This is your own SimpleX address!" xml:space="preserve">
|
||||
<source>Connect to yourself?
|
||||
This is your own SimpleX address!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect to yourself? This is your own one-time link!" xml:space="preserve">
|
||||
<source>Connect to yourself?
|
||||
This is your own one-time link!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via contact address" xml:space="preserve">
|
||||
<source>Connect via contact address</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via link" xml:space="preserve">
|
||||
@@ -1125,6 +1218,18 @@
|
||||
<target>Свързване чрез еднократен линк за връзка</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect with %@" xml:space="preserve">
|
||||
<source>Connect with %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connected desktop" xml:space="preserve">
|
||||
<source>Connected desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connected to desktop" xml:space="preserve">
|
||||
<source>Connected to desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connecting server…" xml:space="preserve">
|
||||
<source>Connecting to server…</source>
|
||||
<target>Свързване със сървъра…</target>
|
||||
@@ -1135,6 +1240,10 @@
|
||||
<target>Свързване със сървър…(грешка: %@)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connecting to desktop" xml:space="preserve">
|
||||
<source>Connecting to desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection" xml:space="preserve">
|
||||
<source>Connection</source>
|
||||
<target>Връзка</target>
|
||||
@@ -1155,6 +1264,10 @@
|
||||
<target>Заявката за връзка е изпратена!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection terminated" xml:space="preserve">
|
||||
<source>Connection terminated</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection timeout" xml:space="preserve">
|
||||
<source>Connection timeout</source>
|
||||
<target>Времето на изчакване за установяване на връзката изтече</target>
|
||||
@@ -1170,11 +1283,6 @@
|
||||
<target>Контактът вече съществува</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Contact and all messages will be deleted - this cannot be undone!" xml:space="preserve">
|
||||
<source>Contact and all messages will be deleted - this cannot be undone!</source>
|
||||
<target>Контактът и всички съобщения ще бъдат изтрити - това не може да бъде отменено!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Contact hidden:" xml:space="preserve">
|
||||
<source>Contact hidden:</source>
|
||||
<target>Контактът е скрит:</target>
|
||||
@@ -1225,6 +1333,10 @@
|
||||
<target>Версия на ядрото: v%@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Correct name to %@?" xml:space="preserve">
|
||||
<source>Correct name to %@?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create" xml:space="preserve">
|
||||
<source>Create</source>
|
||||
<target>Създай</target>
|
||||
@@ -1235,6 +1347,10 @@
|
||||
<target>Създай SimpleX адрес</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create a group using a random profile." xml:space="preserve">
|
||||
<source>Create a group using a random profile.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create an address to let people connect with you." xml:space="preserve">
|
||||
<source>Create an address to let people connect with you.</source>
|
||||
<target>Създайте адрес, за да позволите на хората да се свързват с вас.</target>
|
||||
@@ -1245,6 +1361,10 @@
|
||||
<target>Създай файл</target>
|
||||
<note>server test step</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create group" xml:space="preserve">
|
||||
<source>Create group</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create group link" xml:space="preserve">
|
||||
<source>Create group link</source>
|
||||
<target>Създай групов линк</target>
|
||||
@@ -1265,6 +1385,10 @@
|
||||
<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>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create queue" xml:space="preserve">
|
||||
<source>Create queue</source>
|
||||
<target>Създай опашка</target>
|
||||
@@ -1423,6 +1547,10 @@
|
||||
<target>Изтрий</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete %lld messages?" xml:space="preserve">
|
||||
<source>Delete %lld messages?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete Contact" xml:space="preserve">
|
||||
<source>Delete Contact</source>
|
||||
<target>Изтрий контакт</target>
|
||||
@@ -1448,6 +1576,10 @@
|
||||
<target>Изтрий всички файлове</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete and notify contact" xml:space="preserve">
|
||||
<source>Delete and notify contact</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete archive" xml:space="preserve">
|
||||
<source>Delete archive</source>
|
||||
<target>Изтрий архив</target>
|
||||
@@ -1478,9 +1610,9 @@
|
||||
<target>Изтрий контакт</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete contact?" xml:space="preserve">
|
||||
<source>Delete contact?</source>
|
||||
<target>Изтрий контакт?</target>
|
||||
<trans-unit id="Delete contact? This cannot be undone!" xml:space="preserve">
|
||||
<source>Delete contact?
|
||||
This cannot be undone!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete database" xml:space="preserve">
|
||||
@@ -1623,6 +1755,18 @@
|
||||
<target>Описание</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Desktop address" xml:space="preserve">
|
||||
<source>Desktop address</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Desktop app version %@ is not compatible with this app." xml:space="preserve">
|
||||
<source>Desktop app version %@ is not compatible with this app.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Desktop devices" xml:space="preserve">
|
||||
<source>Desktop devices</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Develop" xml:space="preserve">
|
||||
<source>Develop</source>
|
||||
<target>Разработване</target>
|
||||
@@ -1713,19 +1857,17 @@
|
||||
<target>Прекъсни връзката</target>
|
||||
<note>server test step</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disconnect desktop?" xml:space="preserve">
|
||||
<source>Disconnect desktop?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Discover and join groups" xml:space="preserve">
|
||||
<source>Discover and join groups</source>
|
||||
<target>Открийте и се присъединете към групи</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Display name" xml:space="preserve">
|
||||
<source>Display name</source>
|
||||
<target>Показвано Име</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Display name:" xml:space="preserve">
|
||||
<source>Display name:</source>
|
||||
<target>Показвано име:</target>
|
||||
<trans-unit id="Discover via local network" xml:space="preserve">
|
||||
<source>Discover via local network</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
|
||||
@@ -1898,6 +2040,14 @@
|
||||
<target>Криптирано съобщение: неочаквана грешка</target>
|
||||
<note>notification</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Encryption re-negotiation error" xml:space="preserve">
|
||||
<source>Encryption re-negotiation error</source>
|
||||
<note>message decrypt error item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Encryption re-negotiation failed." xml:space="preserve">
|
||||
<source>Encryption re-negotiation failed.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter Passcode" xml:space="preserve">
|
||||
<source>Enter Passcode</source>
|
||||
<target>Въведете kодa за достъп</target>
|
||||
@@ -1908,6 +2058,10 @@
|
||||
<target>Въведи правилна парола.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter group name…" xml:space="preserve">
|
||||
<source>Enter group name…</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter passphrase…" xml:space="preserve">
|
||||
<source>Enter passphrase…</source>
|
||||
<target>Въведи парола…</target>
|
||||
@@ -1923,6 +2077,10 @@
|
||||
<target>Въведи сървъра ръчно</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter this device name…" xml:space="preserve">
|
||||
<source>Enter this device name…</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter welcome message…" xml:space="preserve">
|
||||
<source>Enter welcome message…</source>
|
||||
<target>Въведи съобщение при посрещане…</target>
|
||||
@@ -1933,6 +2091,10 @@
|
||||
<target>Въведи съобщение при посрещане…(незадължително)</target>
|
||||
<note>placeholder</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter your name…" xml:space="preserve">
|
||||
<source>Enter your name…</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error" xml:space="preserve">
|
||||
<source>Error</source>
|
||||
<target>Грешка при свързване със сървъра</target>
|
||||
@@ -1990,6 +2152,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Error creating member contact" xml:space="preserve">
|
||||
<source>Error creating member contact</source>
|
||||
<target>Грешка при създаване на контакт с член</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error creating profile!" xml:space="preserve">
|
||||
@@ -2124,6 +2287,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Error sending member contact invitation" xml:space="preserve">
|
||||
<source>Error sending member contact invitation</source>
|
||||
<target>Грешка при изпращане на съобщение за покана за контакт</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error sending message" xml:space="preserve">
|
||||
@@ -2206,6 +2370,10 @@
|
||||
<target>Изход без запазване</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Expand" xml:space="preserve">
|
||||
<source>Expand</source>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Export database" xml:space="preserve">
|
||||
<source>Export database</source>
|
||||
<target>Експортирай база данни</target>
|
||||
@@ -2236,6 +2404,10 @@
|
||||
<target>Бързо и без чакане, докато подателят е онлайн!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Faster joining and more reliable messages." xml:space="preserve">
|
||||
<source>Faster joining and more reliable messages.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Favorite" xml:space="preserve">
|
||||
<source>Favorite</source>
|
||||
<target>Любим</target>
|
||||
@@ -2331,6 +2503,10 @@
|
||||
<target>За конзолата</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Found desktop" xml:space="preserve">
|
||||
<source>Found desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="French interface" xml:space="preserve">
|
||||
<source>French interface</source>
|
||||
<target>Френски интерфейс</target>
|
||||
@@ -2351,6 +2527,10 @@
|
||||
<target>Пълно име:</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Fully decentralized – visible only to members." xml:space="preserve">
|
||||
<source>Fully decentralized – visible only to members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Fully re-implemented - work in background!" xml:space="preserve">
|
||||
<source>Fully re-implemented - work in background!</source>
|
||||
<target>Напълно преработено - работи във фонов режим!</target>
|
||||
@@ -2371,6 +2551,14 @@
|
||||
<target>Група</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group already exists" xml:space="preserve">
|
||||
<source>Group already exists</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group already exists!" xml:space="preserve">
|
||||
<source>Group already exists!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group display name" xml:space="preserve">
|
||||
<source>Group display name</source>
|
||||
<target>Показвано име на групата</target>
|
||||
@@ -2641,6 +2829,10 @@
|
||||
<target>Инкогнито</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incognito groups" xml:space="preserve">
|
||||
<source>Incognito groups</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incognito mode" xml:space="preserve">
|
||||
<source>Incognito mode</source>
|
||||
<target>Режим инкогнито</target>
|
||||
@@ -2671,6 +2863,10 @@
|
||||
<target>Несъвместима версия на базата данни</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incompatible version" xml:space="preserve">
|
||||
<source>Incompatible version</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incorrect passcode" xml:space="preserve">
|
||||
<source>Incorrect passcode</source>
|
||||
<target>Неправилен kод за достъп</target>
|
||||
@@ -2718,6 +2914,10 @@
|
||||
<target>Невалиден линк за връзка</target>
|
||||
<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 server address!" xml:space="preserve">
|
||||
<source>Invalid server address!</source>
|
||||
<target>Невалиден адрес на сървъра!</target>
|
||||
@@ -2809,16 +3009,33 @@
|
||||
<target>Влез в групата</target>
|
||||
<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>
|
||||
</trans-unit>
|
||||
<trans-unit id="Join incognito" xml:space="preserve">
|
||||
<source>Join incognito</source>
|
||||
<target>Влез инкогнито</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Join with current profile" xml:space="preserve">
|
||||
<source>Join with current profile</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Join your group? This is your link for group %@!" xml:space="preserve">
|
||||
<source>Join your group?
|
||||
This is your link for group %@!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Joining group" xml:space="preserve">
|
||||
<source>Joining group</source>
|
||||
<target>Присъединяване към групата</target>
|
||||
<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 your connections" xml:space="preserve">
|
||||
<source>Keep your connections</source>
|
||||
<target>Запазете връзките си</target>
|
||||
@@ -2879,6 +3096,18 @@
|
||||
<target>Ограничения</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Link mobile and desktop apps! 🔗" xml:space="preserve">
|
||||
<source>Link mobile and desktop apps! 🔗</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Linked desktop options" xml:space="preserve">
|
||||
<source>Linked desktop options</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Linked desktops" xml:space="preserve">
|
||||
<source>Linked desktops</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Live message!" xml:space="preserve">
|
||||
<source>Live message!</source>
|
||||
<target>Съобщение на живо!</target>
|
||||
@@ -3029,6 +3258,10 @@
|
||||
<target>Съобщения и файлове</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Messages from %@ will be shown!" xml:space="preserve">
|
||||
<source>Messages from %@ will be shown!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrating database archive…" xml:space="preserve">
|
||||
<source>Migrating database archive…</source>
|
||||
<target>Архивът на базата данни се мигрира…</target>
|
||||
@@ -3224,6 +3457,10 @@
|
||||
<target>Няма получени или изпратени файлове</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Not compatible!" xml:space="preserve">
|
||||
<source>Not compatible!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Notifications" xml:space="preserve">
|
||||
<source>Notifications</source>
|
||||
<target>Известия</target>
|
||||
@@ -3360,6 +3597,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Open" xml:space="preserve">
|
||||
<source>Open</source>
|
||||
<target>Отвори</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open Settings" xml:space="preserve">
|
||||
@@ -3377,6 +3615,10 @@
|
||||
<target>Отвори конзолата</target>
|
||||
<note>authentication reason</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open group" xml:space="preserve">
|
||||
<source>Open group</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open user profiles" xml:space="preserve">
|
||||
<source>Open user profiles</source>
|
||||
<target>Отвори потребителските профили</target>
|
||||
@@ -3392,11 +3634,6 @@
|
||||
<target>Отваряне на база данни…</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red." xml:space="preserve">
|
||||
<source>Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red.</source>
|
||||
<target>Отварянето на линка в браузъра може да намали поверителността и сигурността на връзката. Несигурните SimpleX линкове ще бъдат червени.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="PING count" xml:space="preserve">
|
||||
<source>PING count</source>
|
||||
<target>PING бройка</target>
|
||||
@@ -3442,6 +3679,10 @@
|
||||
<target>Постави</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste desktop address" xml:space="preserve">
|
||||
<source>Paste desktop address</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste image" xml:space="preserve">
|
||||
<source>Paste image</source>
|
||||
<target>Постави изображение</target>
|
||||
@@ -3587,6 +3828,14 @@
|
||||
<target>Профилно изображение</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Profile name" xml:space="preserve">
|
||||
<source>Profile name</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Profile name:" xml:space="preserve">
|
||||
<source>Profile name:</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Profile password" xml:space="preserve">
|
||||
<source>Profile password</source>
|
||||
<target>Профилна парола</target>
|
||||
@@ -3832,6 +4081,14 @@
|
||||
<target>Предоговори криптирането?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Repeat connection request?" xml:space="preserve">
|
||||
<source>Repeat connection request?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Repeat join request?" xml:space="preserve">
|
||||
<source>Repeat join request?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reply" xml:space="preserve">
|
||||
<source>Reply</source>
|
||||
<target>Отговори</target>
|
||||
@@ -4017,6 +4274,10 @@
|
||||
<target>Сканирай QR код</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Scan QR code from desktop" xml:space="preserve">
|
||||
<source>Scan QR code from desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Scan code" xml:space="preserve">
|
||||
<source>Scan code</source>
|
||||
<target>Сканирай код</target>
|
||||
@@ -4099,6 +4360,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Send direct message to connect" xml:space="preserve">
|
||||
<source>Send direct message to connect</source>
|
||||
<target>Изпрати лично съобщение за свързване</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Send disappearing message" xml:space="preserve">
|
||||
@@ -4236,6 +4498,10 @@
|
||||
<target>Сървъри</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Session code" xml:space="preserve">
|
||||
<source>Session code</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Set 1 day" xml:space="preserve">
|
||||
<source>Set 1 day</source>
|
||||
<target>Задай 1 ден</target>
|
||||
@@ -4546,6 +4812,10 @@
|
||||
<target>Докосни бутона </target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to Connect" xml:space="preserve">
|
||||
<source>Tap to Connect</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to activate profile." xml:space="preserve">
|
||||
<source>Tap to activate profile.</source>
|
||||
<target>Докосни за активиране на профил.</target>
|
||||
@@ -4643,11 +4913,6 @@ 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 group is fully decentralized – it is visible only to the members." xml:space="preserve">
|
||||
<source>The group is fully decentralized – it is visible only to the members.</source>
|
||||
<target>Групата е напълно децентрализирана – видима е само за членовете.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The hash of the previous message is different." xml:space="preserve">
|
||||
<source>The hash of the previous message is different.</source>
|
||||
<target>Хешът на предишното съобщение е различен.</target>
|
||||
@@ -4733,6 +4998,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="This device name" xml:space="preserve">
|
||||
<source>This device 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>
|
||||
@@ -4743,6 +5012,14 @@ 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="This is your own SimpleX address!" xml:space="preserve">
|
||||
<source>This is your own SimpleX address!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This is your own one-time link!" xml:space="preserve">
|
||||
<source>This is your own one-time link!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This setting applies to messages in your current chat profile **%@**." xml:space="preserve">
|
||||
<source>This setting applies to messages in your current chat profile **%@**.</source>
|
||||
<target>Тази настройка се прилага за съобщения в текущия ви профил **%@**.</target>
|
||||
@@ -4758,6 +5035,10 @@ It can happen because of some bug or when the connection is compromised.</source
|
||||
<target>За да се свърже, вашият контакт може да сканира QR код или да използва линка в приложението.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="To hide unwanted messages." xml:space="preserve">
|
||||
<source>To hide unwanted messages.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="To make a new connection" xml:space="preserve">
|
||||
<source>To make a new connection</source>
|
||||
<target>За да направите нова връзка</target>
|
||||
@@ -4840,6 +5121,18 @@ 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="Unblock" xml:space="preserve">
|
||||
<source>Unblock</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?" xml:space="preserve">
|
||||
<source>Unblock member?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unexpected error: %@" xml:space="preserve">
|
||||
<source>Unexpected error: %@</source>
|
||||
<target>Неочаквана грешка: %@</target>
|
||||
@@ -4902,6 +5195,14 @@ To connect, please ask your contact to create another connection link and check
|
||||
За да се свържете, моля, помолете вашия контакт да създаде друг линк за връзка и проверете дали имате стабилна мрежова връзка.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unlink" xml:space="preserve">
|
||||
<source>Unlink</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unlink desktop?" xml:space="preserve">
|
||||
<source>Unlink desktop?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unlock" xml:space="preserve">
|
||||
<source>Unlock</source>
|
||||
<target>Отключи</target>
|
||||
@@ -4992,6 +5293,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 from desktop" xml:space="preserve">
|
||||
<source>Use from desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Use iOS call interface" xml:space="preserve">
|
||||
<source>Use iOS call interface</source>
|
||||
<target>Използвай интерфейса за повикване на iOS</target>
|
||||
@@ -5022,11 +5327,23 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>Използват се сървърите на SimpleX Chat.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify code with desktop" xml:space="preserve">
|
||||
<source>Verify code with desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify connection" xml:space="preserve">
|
||||
<source>Verify connection</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify connection security" xml:space="preserve">
|
||||
<source>Verify connection security</source>
|
||||
<target>Потвръди сигурността на връзката</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify connections" xml:space="preserve">
|
||||
<source>Verify connections</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify security code" xml:space="preserve">
|
||||
<source>Verify security code</source>
|
||||
<target>Потвръди кода за сигурност</target>
|
||||
@@ -5037,6 +5354,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="Via secure quantum resistant protocol." xml:space="preserve">
|
||||
<source>Via secure quantum resistant protocol.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Video call" xml:space="preserve">
|
||||
<source>Video call</source>
|
||||
<target>Видео разговор</target>
|
||||
@@ -5087,6 +5408,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="Waiting for desktop..." xml:space="preserve">
|
||||
<source>Waiting for desktop...</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Waiting for file" xml:space="preserve">
|
||||
<source>Waiting for file</source>
|
||||
<target>Изчаква се получаването на файла</target>
|
||||
@@ -5187,6 +5512,35 @@ 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="You are already connecting to %@." xml:space="preserve">
|
||||
<source>You are already connecting to %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are already connecting via this one-time link!" xml:space="preserve">
|
||||
<source>You are already connecting via this one-time link!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are already in group %@." xml:space="preserve">
|
||||
<source>You are already in group %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are already joining the group %@." xml:space="preserve">
|
||||
<source>You are already joining the group %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are already joining the group via this link!" xml:space="preserve">
|
||||
<source>You are already joining the group via this link!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are already joining the group via this link." xml:space="preserve">
|
||||
<source>You are already joining the group via this link.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are already joining the group! Repeat join request?" xml:space="preserve">
|
||||
<source>You are already joining the group!
|
||||
Repeat join request?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are connected to the server used to receive messages from this contact." xml:space="preserve">
|
||||
<source>You are connected to the server used to receive messages from this contact.</source>
|
||||
<target>Вие сте свързани към сървъра, използван за получаване на съобщения от този контакт.</target>
|
||||
@@ -5282,6 +5636,15 @@ 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="You have already requested connection via this address!" xml:space="preserve">
|
||||
<source>You have already requested connection via this address!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You have already requested connection! Repeat connection request?" xml:space="preserve">
|
||||
<source>You have already requested connection!
|
||||
Repeat connection request?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You have no chats" xml:space="preserve">
|
||||
<source>You have no chats</source>
|
||||
<target>Нямате чатове</target>
|
||||
@@ -5332,6 +5695,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="You will be connected when group link host's device is online, please wait or check later!" xml:space="preserve">
|
||||
<source>You will be connected when group link host's device is online, please wait or check later!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You will be connected when your connection request is accepted, please wait or check later!" xml:space="preserve">
|
||||
<source>You will be connected when your connection request is accepted, please wait or check later!</source>
|
||||
<target>Ще бъдете свързани, когато заявката ви за връзка бъде приета, моля, изчакайте или проверете по-късно!</target>
|
||||
@@ -5347,9 +5714,8 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>Ще трябва да се идентифицирате, когато стартирате или възобновите приложението след 30 секунди във фонов режим.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You will join a group this link refers to and connect to its group members." xml:space="preserve">
|
||||
<source>You will join a group this link refers to and connect to its group members.</source>
|
||||
<target>Ще се присъедините към групата, към която този линк препраща, и ще се свържете с нейните членове.</target>
|
||||
<trans-unit id="You will connect to all group members." xml:space="preserve">
|
||||
<source>You will connect to all group members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You will still receive calls and notifications from muted profiles when they are active." xml:space="preserve">
|
||||
@@ -5417,11 +5783,6 @@ 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="Your chat profile will be sent to group members" xml:space="preserve">
|
||||
<source>Your chat profile will be sent to group members</source>
|
||||
<target>Вашият чат профил ще бъде изпратен на членовете на групата</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your chat profiles" xml:space="preserve">
|
||||
<source>Your chat profiles</source>
|
||||
<target>Вашите чат профили</target>
|
||||
@@ -5476,6 +5837,10 @@ You can change it in Settings.</source>
|
||||
<target>Вашата поверителност</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your profile" xml:space="preserve">
|
||||
<source>Your profile</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your profile **%@** will be shared." xml:space="preserve">
|
||||
<source>Your profile **%@** will be shared.</source>
|
||||
<target>Вашият профил **%@** ще бъде споделен.</target>
|
||||
@@ -5568,11 +5933,19 @@ SimpleX сървърите не могат да видят вашия профи
|
||||
<target>винаги</target>
|
||||
<note>pref value</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="and %lld other events" xml:space="preserve">
|
||||
<source>and %lld other events</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="audio call (not e2e encrypted)" xml:space="preserve">
|
||||
<source>audio call (not e2e encrypted)</source>
|
||||
<target>аудио разговор (не е e2e криптиран)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="author" xml:space="preserve">
|
||||
<source>author</source>
|
||||
<note>member role</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="bad message ID" xml:space="preserve">
|
||||
<source>bad message ID</source>
|
||||
<target>лошо ID на съобщението</target>
|
||||
@@ -5583,6 +5956,10 @@ SimpleX сървърите не могат да видят вашия профи
|
||||
<target>лош хеш на съобщението</target>
|
||||
<note>integrity error chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="blocked" xml:space="preserve">
|
||||
<source>blocked</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="bold" xml:space="preserve">
|
||||
<source>bold</source>
|
||||
<target>удебелен</target>
|
||||
@@ -5655,6 +6032,7 @@ SimpleX сървърите не могат да видят вашия профи
|
||||
</trans-unit>
|
||||
<trans-unit id="connected directly" xml:space="preserve">
|
||||
<source>connected directly</source>
|
||||
<target>свързан директно</target>
|
||||
<note>rcv group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="connecting" xml:space="preserve">
|
||||
@@ -5752,6 +6130,10 @@ SimpleX сървърите не могат да видят вашия профи
|
||||
<target>изтрит</target>
|
||||
<note>deleted chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="deleted contact" xml:space="preserve">
|
||||
<source>deleted contact</source>
|
||||
<note>rcv direct event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="deleted group" xml:space="preserve">
|
||||
<source>deleted group</source>
|
||||
<target>групата изтрита</target>
|
||||
@@ -6036,7 +6418,8 @@ SimpleX сървърите не могат да видят вашия профи
|
||||
<source>off</source>
|
||||
<target>изключено</target>
|
||||
<note>enabled status
|
||||
group pref value</note>
|
||||
group pref value
|
||||
time to disappear</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="offered %@" xml:space="preserve">
|
||||
<source>offered %@</source>
|
||||
@@ -6053,11 +6436,6 @@ SimpleX сървърите не могат да видят вашия профи
|
||||
<target>включено</target>
|
||||
<note>group pref value</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="or chat with the developers" xml:space="preserve">
|
||||
<source>or chat with the developers</source>
|
||||
<target>или пишете на разработчиците</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="owner" xml:space="preserve">
|
||||
<source>owner</source>
|
||||
<target>собственик</target>
|
||||
@@ -6120,6 +6498,7 @@ SimpleX сървърите не могат да видят вашия профи
|
||||
</trans-unit>
|
||||
<trans-unit id="send direct message" xml:space="preserve">
|
||||
<source>send direct message</source>
|
||||
<target>изпрати лично съобщение</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="starting…" xml:space="preserve">
|
||||
@@ -6147,6 +6526,10 @@ SimpleX сървърите не могат да видят вашия профи
|
||||
<target>актуализиран профил на групата</target>
|
||||
<note>rcv group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="v%@" xml:space="preserve">
|
||||
<source>v%@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="v%@ (%@)" xml:space="preserve">
|
||||
<source>v%@ (%@)</source>
|
||||
<target>v%@ (%@)</target>
|
||||
@@ -6284,6 +6667,10 @@ SimpleX сървърите не могат да видят вашия профи
|
||||
<target>SimpleX използва Face ID за локалнa идентификация</target>
|
||||
<note>Privacy - Face ID Usage Description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="NSLocalNetworkUsageDescription" xml:space="preserve">
|
||||
<source>SimpleX uses local network access to allow using user chat profile via desktop app on the same network.</source>
|
||||
<note>Privacy - Local Network Usage Description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="NSMicrophoneUsageDescription" xml:space="preserve">
|
||||
<source>SimpleX needs microphone access for audio and video calls, and to record voice messages.</source>
|
||||
<target>SimpleX се нуждае от достъп до микрофона за аудио и видео разговори и за запис на гласови съобщения.</target>
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
"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 */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,8 @@
|
||||
"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 */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,8 @@
|
||||
"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 */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,8 @@
|
||||
"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 */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,8 @@
|
||||
"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 */
|
||||
|
||||
@@ -87,6 +87,10 @@
|
||||
<target>%@ / % @</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ and %@" xml:space="preserve">
|
||||
<source>%@ and %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ and %@ connected" xml:space="preserve">
|
||||
<source>%@ and %@ connected</source>
|
||||
<target>%@ ja %@ yhdistetty</target>
|
||||
@@ -97,6 +101,10 @@
|
||||
<target>%1$@ klo %2$@:</target>
|
||||
<note>copied message info, <sender> at <time></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ connected" xml:space="preserve">
|
||||
<source>%@ connected</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ is connected!" xml:space="preserve">
|
||||
<source>%@ is connected!</source>
|
||||
<target>%@ on yhdistetty!</target>
|
||||
@@ -122,6 +130,10 @@
|
||||
<target>%@ haluaa muodostaa yhteyden!</target>
|
||||
<note>notification title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@, %@ and %lld members" xml:space="preserve">
|
||||
<source>%@, %@ and %lld members</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@, %@ and %lld other members connected" xml:space="preserve">
|
||||
<source>%@, %@ and %lld other members connected</source>
|
||||
<target>%@, %@ ja %lld muut jäsenet yhdistetty</target>
|
||||
@@ -187,11 +199,27 @@
|
||||
<target>%lld tiedosto(a), joiden kokonaiskoko on %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld group events" xml:space="preserve">
|
||||
<source>%lld group events</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld members" xml:space="preserve">
|
||||
<source>%lld members</source>
|
||||
<target>%lld jäsenet</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld messages blocked" xml:space="preserve">
|
||||
<source>%lld messages blocked</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>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld messages moderated by %@" xml:space="preserve">
|
||||
<source>%lld messages moderated by %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld minutes" xml:space="preserve">
|
||||
<source>%lld minutes</source>
|
||||
<target>%lld minuuttia</target>
|
||||
@@ -199,6 +227,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld new interface languages" xml:space="preserve">
|
||||
<source>%lld new interface languages</source>
|
||||
<target>%lld uutta käyttöliittymän kieltä</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld second(s)" xml:space="preserve">
|
||||
@@ -261,6 +290,14 @@
|
||||
<target>(</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="(new)" xml:space="preserve">
|
||||
<source>(new)</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="(this device v%@)" xml:space="preserve">
|
||||
<source>(this device v%@)</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id=")" xml:space="preserve">
|
||||
<source>)</source>
|
||||
<target>)</target>
|
||||
@@ -346,6 +383,12 @@
|
||||
- ja paljon muuta!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="- optionally notify deleted contacts. - profile names with spaces. - and more!" xml:space="preserve">
|
||||
<source>- optionally notify deleted contacts.
|
||||
- profile names with spaces.
|
||||
- and more!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="- voice messages up to 5 minutes. - custom time to disappear. - editing history." xml:space="preserve">
|
||||
<source>- voice messages up to 5 minutes.
|
||||
- custom time to disappear.
|
||||
@@ -360,6 +403,10 @@
|
||||
<target>.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="0 sec" xml:space="preserve">
|
||||
<source>0 sec</source>
|
||||
<note>time to disappear</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="0s" xml:space="preserve">
|
||||
<source>0s</source>
|
||||
<target>0s</target>
|
||||
@@ -585,6 +632,10 @@
|
||||
<target>Kaikki viestit poistetaan - tätä ei voi kumota! Viestit poistuvat VAIN sinulta.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All new messages from %@ will be hidden!" xml:space="preserve">
|
||||
<source>All new messages from %@ will be hidden!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All your contacts will remain connected." xml:space="preserve">
|
||||
<source>All your contacts will remain connected.</source>
|
||||
<target>Kaikki kontaktisi pysyvät yhteydessä.</target>
|
||||
@@ -690,6 +741,14 @@
|
||||
<target>Oletko jo muodostanut yhteyden?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Already connecting!" xml:space="preserve">
|
||||
<source>Already connecting!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Already joining the group!" xml:space="preserve">
|
||||
<source>Already joining the group!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Always use relay" xml:space="preserve">
|
||||
<source>Always use relay</source>
|
||||
<target>Käytä aina relettä</target>
|
||||
@@ -809,6 +868,10 @@
|
||||
<target>Takaisin</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Bad desktop address" xml:space="preserve">
|
||||
<source>Bad desktop address</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Bad message ID" xml:space="preserve">
|
||||
<source>Bad message ID</source>
|
||||
<target>Virheellinen viestin tunniste</target>
|
||||
@@ -819,11 +882,31 @@
|
||||
<target>Virheellinen viestin tarkiste</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better groups" xml:space="preserve">
|
||||
<source>Better groups</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better messages" xml:space="preserve">
|
||||
<source>Better messages</source>
|
||||
<target>Parempia viestejä</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Block" xml:space="preserve">
|
||||
<source>Block</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>
|
||||
</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="Block member?" xml:space="preserve">
|
||||
<source>Block member?</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>
|
||||
@@ -1084,9 +1167,8 @@
|
||||
<target>Yhdistä</target>
|
||||
<note>server test step</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect directly" xml:space="preserve">
|
||||
<source>Connect directly</source>
|
||||
<target>Yhdistä suoraan</target>
|
||||
<trans-unit id="Connect automatically" xml:space="preserve">
|
||||
<source>Connect automatically</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect incognito" xml:space="preserve">
|
||||
@@ -1094,14 +1176,26 @@
|
||||
<target>Yhdistä Incognito</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via contact link" xml:space="preserve">
|
||||
<source>Connect via contact link</source>
|
||||
<target>Yhdistä kontaktilinkillä</target>
|
||||
<trans-unit id="Connect to desktop" xml:space="preserve">
|
||||
<source>Connect to desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via group link?" xml:space="preserve">
|
||||
<source>Connect via group link?</source>
|
||||
<target>Yhdistetäänkö ryhmälinkin kautta?</target>
|
||||
<trans-unit id="Connect to yourself?" xml:space="preserve">
|
||||
<source>Connect to yourself?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect to yourself? This is your own SimpleX address!" xml:space="preserve">
|
||||
<source>Connect to yourself?
|
||||
This is your own SimpleX address!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect to yourself? This is your own one-time link!" xml:space="preserve">
|
||||
<source>Connect to yourself?
|
||||
This is your own one-time link!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via contact address" xml:space="preserve">
|
||||
<source>Connect via contact address</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via link" xml:space="preserve">
|
||||
@@ -1119,6 +1213,18 @@
|
||||
<target>Yhdistä kertalinkillä</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect with %@" xml:space="preserve">
|
||||
<source>Connect with %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connected desktop" xml:space="preserve">
|
||||
<source>Connected desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connected to desktop" xml:space="preserve">
|
||||
<source>Connected to desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connecting server…" xml:space="preserve">
|
||||
<source>Connecting to server…</source>
|
||||
<target>Yhteyden muodostaminen palvelimeen…</target>
|
||||
@@ -1129,6 +1235,10 @@
|
||||
<target>Yhteyden muodostaminen palvelimeen... (virhe: %@)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connecting to desktop" xml:space="preserve">
|
||||
<source>Connecting to desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection" xml:space="preserve">
|
||||
<source>Connection</source>
|
||||
<target>Yhteys</target>
|
||||
@@ -1149,6 +1259,10 @@
|
||||
<target>Yhteyspyyntö lähetetty!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection terminated" xml:space="preserve">
|
||||
<source>Connection terminated</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection timeout" xml:space="preserve">
|
||||
<source>Connection timeout</source>
|
||||
<target>Yhteyden aikakatkaisu</target>
|
||||
@@ -1164,11 +1278,6 @@
|
||||
<target>Kontakti on jo olemassa</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Contact and all messages will be deleted - this cannot be undone!" xml:space="preserve">
|
||||
<source>Contact and all messages will be deleted - this cannot be undone!</source>
|
||||
<target>Kontakti ja kaikki viestit poistetaan - tätä ei voi perua!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Contact hidden:" xml:space="preserve">
|
||||
<source>Contact hidden:</source>
|
||||
<target>Kontakti piilotettu:</target>
|
||||
@@ -1219,6 +1328,10 @@
|
||||
<target>Ydinversio: v%@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Correct name to %@?" xml:space="preserve">
|
||||
<source>Correct name to %@?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create" xml:space="preserve">
|
||||
<source>Create</source>
|
||||
<target>Luo</target>
|
||||
@@ -1229,6 +1342,10 @@
|
||||
<target>Luo SimpleX-osoite</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create a group using a random profile." xml:space="preserve">
|
||||
<source>Create a group using a random profile.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create an address to let people connect with you." xml:space="preserve">
|
||||
<source>Create an address to let people connect with you.</source>
|
||||
<target>Luo osoite, jolla ihmiset voivat ottaa sinuun yhteyttä.</target>
|
||||
@@ -1239,6 +1356,10 @@
|
||||
<target>Luo tiedosto</target>
|
||||
<note>server test step</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create group" xml:space="preserve">
|
||||
<source>Create group</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create group link" xml:space="preserve">
|
||||
<source>Create group link</source>
|
||||
<target>Luo ryhmälinkki</target>
|
||||
@@ -1251,6 +1372,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" xml:space="preserve">
|
||||
<source>Create new profile in [desktop app](https://simplex.chat/downloads/). 💻</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">
|
||||
@@ -1258,6 +1380,10 @@
|
||||
<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>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create queue" xml:space="preserve">
|
||||
<source>Create queue</source>
|
||||
<target>Luo jono</target>
|
||||
@@ -1416,6 +1542,10 @@
|
||||
<target>Poista</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete %lld messages?" xml:space="preserve">
|
||||
<source>Delete %lld messages?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete Contact" xml:space="preserve">
|
||||
<source>Delete Contact</source>
|
||||
<target>Poista kontakti</target>
|
||||
@@ -1441,6 +1571,10 @@
|
||||
<target>Poista kaikki tiedostot</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete and notify contact" xml:space="preserve">
|
||||
<source>Delete and notify contact</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete archive" xml:space="preserve">
|
||||
<source>Delete archive</source>
|
||||
<target>Poista arkisto</target>
|
||||
@@ -1471,9 +1605,9 @@
|
||||
<target>Poista kontakti</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete contact?" xml:space="preserve">
|
||||
<source>Delete contact?</source>
|
||||
<target>Poista kontakti?</target>
|
||||
<trans-unit id="Delete contact? This cannot be undone!" xml:space="preserve">
|
||||
<source>Delete contact?
|
||||
This cannot be undone!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete database" xml:space="preserve">
|
||||
@@ -1616,6 +1750,18 @@
|
||||
<target>Kuvaus</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Desktop address" xml:space="preserve">
|
||||
<source>Desktop address</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Desktop app version %@ is not compatible with this app." xml:space="preserve">
|
||||
<source>Desktop app version %@ is not compatible with this app.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Desktop devices" xml:space="preserve">
|
||||
<source>Desktop devices</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Develop" xml:space="preserve">
|
||||
<source>Develop</source>
|
||||
<target>Kehitä</target>
|
||||
@@ -1706,18 +1852,17 @@
|
||||
<target>Katkaise</target>
|
||||
<note>server test step</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disconnect desktop?" xml:space="preserve">
|
||||
<source>Disconnect desktop?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Discover and join groups" xml:space="preserve">
|
||||
<source>Discover and join groups</source>
|
||||
<target>Löydä ryhmiä ja liity niihin</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Display name" xml:space="preserve">
|
||||
<source>Display name</source>
|
||||
<target>Näyttönimi</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Display name:" xml:space="preserve">
|
||||
<source>Display name:</source>
|
||||
<target>Näyttönimi:</target>
|
||||
<trans-unit id="Discover via local network" xml:space="preserve">
|
||||
<source>Discover via local network</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
|
||||
@@ -1889,6 +2034,14 @@
|
||||
<target>Salattu viesti: odottamaton virhe</target>
|
||||
<note>notification</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Encryption re-negotiation error" xml:space="preserve">
|
||||
<source>Encryption re-negotiation error</source>
|
||||
<note>message decrypt error item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Encryption re-negotiation failed." xml:space="preserve">
|
||||
<source>Encryption re-negotiation failed.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter Passcode" xml:space="preserve">
|
||||
<source>Enter Passcode</source>
|
||||
<target>Syötä pääsykoodi</target>
|
||||
@@ -1899,6 +2052,10 @@
|
||||
<target>Anna oikea tunnuslause.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter group name…" xml:space="preserve">
|
||||
<source>Enter group name…</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter passphrase…" xml:space="preserve">
|
||||
<source>Enter passphrase…</source>
|
||||
<target>Syötä tunnuslause…</target>
|
||||
@@ -1914,6 +2071,10 @@
|
||||
<target>Syötä palvelin manuaalisesti</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter this device name…" xml:space="preserve">
|
||||
<source>Enter this device name…</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter welcome message…" xml:space="preserve">
|
||||
<source>Enter welcome message…</source>
|
||||
<target>Kirjoita tervetuloviesti…</target>
|
||||
@@ -1924,6 +2085,10 @@
|
||||
<target>Kirjoita tervetuloviesti... (valinnainen)</target>
|
||||
<note>placeholder</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter your name…" xml:space="preserve">
|
||||
<source>Enter your name…</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error" xml:space="preserve">
|
||||
<source>Error</source>
|
||||
<target>Virhe</target>
|
||||
@@ -2197,6 +2362,10 @@
|
||||
<target>Poistu tallentamatta</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Expand" xml:space="preserve">
|
||||
<source>Expand</source>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Export database" xml:space="preserve">
|
||||
<source>Export database</source>
|
||||
<target>Vie tietokanta</target>
|
||||
@@ -2227,6 +2396,10 @@
|
||||
<target>Nopea ja ei odotusta, kunnes lähettäjä on online-tilassa!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Faster joining and more reliable messages." xml:space="preserve">
|
||||
<source>Faster joining and more reliable messages.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Favorite" xml:space="preserve">
|
||||
<source>Favorite</source>
|
||||
<target>Suosikki</target>
|
||||
@@ -2322,6 +2495,10 @@
|
||||
<target>Konsoliin</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Found desktop" xml:space="preserve">
|
||||
<source>Found desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="French interface" xml:space="preserve">
|
||||
<source>French interface</source>
|
||||
<target>Ranskalainen käyttöliittymä</target>
|
||||
@@ -2342,6 +2519,10 @@
|
||||
<target>Koko nimi:</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Fully decentralized – visible only to members." xml:space="preserve">
|
||||
<source>Fully decentralized – visible only to members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Fully re-implemented - work in background!" xml:space="preserve">
|
||||
<source>Fully re-implemented - work in background!</source>
|
||||
<target>Täysin uudistettu - toimii taustalla!</target>
|
||||
@@ -2362,6 +2543,14 @@
|
||||
<target>Ryhmä</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group already exists" xml:space="preserve">
|
||||
<source>Group already exists</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group already exists!" xml:space="preserve">
|
||||
<source>Group already exists!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group display name" xml:space="preserve">
|
||||
<source>Group display name</source>
|
||||
<target>Ryhmän näyttönimi</target>
|
||||
@@ -2632,6 +2821,10 @@
|
||||
<target>Incognito</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incognito groups" xml:space="preserve">
|
||||
<source>Incognito groups</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incognito mode" xml:space="preserve">
|
||||
<source>Incognito mode</source>
|
||||
<target>Incognito-tila</target>
|
||||
@@ -2662,6 +2855,10 @@
|
||||
<target>Yhteensopimaton tietokantaversio</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incompatible version" xml:space="preserve">
|
||||
<source>Incompatible version</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incorrect passcode" xml:space="preserve">
|
||||
<source>Incorrect passcode</source>
|
||||
<target>Väärä pääsykoodi</target>
|
||||
@@ -2709,6 +2906,10 @@
|
||||
<target>Virheellinen yhteyslinkki</target>
|
||||
<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 server address!" xml:space="preserve">
|
||||
<source>Invalid server address!</source>
|
||||
<target>Virheellinen palvelinosoite!</target>
|
||||
@@ -2800,16 +3001,33 @@
|
||||
<target>Liity ryhmään</target>
|
||||
<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>
|
||||
</trans-unit>
|
||||
<trans-unit id="Join incognito" xml:space="preserve">
|
||||
<source>Join incognito</source>
|
||||
<target>Liity incognito-tilassa</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Join with current profile" xml:space="preserve">
|
||||
<source>Join with current profile</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Join your group? This is your link for group %@!" xml:space="preserve">
|
||||
<source>Join your group?
|
||||
This is your link for group %@!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Joining group" xml:space="preserve">
|
||||
<source>Joining group</source>
|
||||
<target>Liittyy ryhmään</target>
|
||||
<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 your connections" xml:space="preserve">
|
||||
<source>Keep your connections</source>
|
||||
<target>Pidä kontaktisi</target>
|
||||
@@ -2870,6 +3088,18 @@
|
||||
<target>Rajoitukset</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Link mobile and desktop apps! 🔗" xml:space="preserve">
|
||||
<source>Link mobile and desktop apps! 🔗</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Linked desktop options" xml:space="preserve">
|
||||
<source>Linked desktop options</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Linked desktops" xml:space="preserve">
|
||||
<source>Linked desktops</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Live message!" xml:space="preserve">
|
||||
<source>Live message!</source>
|
||||
<target>Live-viesti!</target>
|
||||
@@ -3020,6 +3250,10 @@
|
||||
<target>Viestit ja tiedostot</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Messages from %@ will be shown!" xml:space="preserve">
|
||||
<source>Messages from %@ will be shown!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrating database archive…" xml:space="preserve">
|
||||
<source>Migrating database archive…</source>
|
||||
<target>Siirretään tietokannan arkistoa…</target>
|
||||
@@ -3214,6 +3448,10 @@
|
||||
<target>Ei vastaanotettuja tai lähetettyjä tiedostoja</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Not compatible!" xml:space="preserve">
|
||||
<source>Not compatible!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Notifications" xml:space="preserve">
|
||||
<source>Notifications</source>
|
||||
<target>Ilmoitukset</target>
|
||||
@@ -3367,6 +3605,10 @@
|
||||
<target>Avaa keskustelukonsoli</target>
|
||||
<note>authentication reason</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open group" xml:space="preserve">
|
||||
<source>Open group</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open user profiles" xml:space="preserve">
|
||||
<source>Open user profiles</source>
|
||||
<target>Avaa käyttäjäprofiilit</target>
|
||||
@@ -3382,11 +3624,6 @@
|
||||
<target>Avataan tietokantaa…</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red." xml:space="preserve">
|
||||
<source>Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red.</source>
|
||||
<target>Linkin avaaminen selaimessa voi heikentää yhteyden yksityisyyttä ja turvallisuutta. Epäluotetut SimpleX-linkit näkyvät punaisina.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="PING count" xml:space="preserve">
|
||||
<source>PING count</source>
|
||||
<target>PING-määrä</target>
|
||||
@@ -3432,6 +3669,10 @@
|
||||
<target>Liitä</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste desktop address" xml:space="preserve">
|
||||
<source>Paste desktop address</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste image" xml:space="preserve">
|
||||
<source>Paste image</source>
|
||||
<target>Liitä kuva</target>
|
||||
@@ -3577,6 +3818,14 @@
|
||||
<target>Profiilikuva</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Profile name" xml:space="preserve">
|
||||
<source>Profile name</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Profile name:" xml:space="preserve">
|
||||
<source>Profile name:</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Profile password" xml:space="preserve">
|
||||
<source>Profile password</source>
|
||||
<target>Profiilin salasana</target>
|
||||
@@ -3822,6 +4071,14 @@
|
||||
<target>Uudelleenneuvottele salaus?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Repeat connection request?" xml:space="preserve">
|
||||
<source>Repeat connection request?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Repeat join request?" xml:space="preserve">
|
||||
<source>Repeat join request?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reply" xml:space="preserve">
|
||||
<source>Reply</source>
|
||||
<target>Vastaa</target>
|
||||
@@ -4007,6 +4264,10 @@
|
||||
<target>Skannaa QR-koodi</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Scan QR code from desktop" xml:space="preserve">
|
||||
<source>Scan QR code from desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Scan code" xml:space="preserve">
|
||||
<source>Scan code</source>
|
||||
<target>Skannaa koodi</target>
|
||||
@@ -4226,6 +4487,10 @@
|
||||
<target>Palvelimet</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Session code" xml:space="preserve">
|
||||
<source>Session code</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Set 1 day" xml:space="preserve">
|
||||
<source>Set 1 day</source>
|
||||
<target>Aseta 1 päivä</target>
|
||||
@@ -4535,6 +4800,10 @@
|
||||
<target>Napauta painiketta </target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to Connect" xml:space="preserve">
|
||||
<source>Tap to Connect</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to activate profile." xml:space="preserve">
|
||||
<source>Tap to activate profile.</source>
|
||||
<target>Aktivoi profiili napauttamalla.</target>
|
||||
@@ -4632,11 +4901,6 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.</t
|
||||
<target>Salaus toimii ja uutta salaussopimusta ei tarvita. Tämä voi johtaa yhteysvirheisiin!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The group is fully decentralized – it is visible only to the members." xml:space="preserve">
|
||||
<source>The group is fully decentralized – it is visible only to the members.</source>
|
||||
<target>Ryhmä on täysin hajautettu - se näkyy vain jäsenille.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The hash of the previous message is different." xml:space="preserve">
|
||||
<source>The hash of the previous message is different.</source>
|
||||
<target>Edellisen viestin tarkiste on erilainen.</target>
|
||||
@@ -4722,6 +4986,10 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.</t
|
||||
<target>Tätä toimintoa ei voi kumota - profiilisi, kontaktisi, viestisi ja tiedostosi poistuvat peruuttamattomasti.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This device name" xml:space="preserve">
|
||||
<source>This device 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>
|
||||
@@ -4732,6 +5000,14 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.</t
|
||||
<target>Tätä ryhmää ei enää ole olemassa.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This is your own SimpleX address!" xml:space="preserve">
|
||||
<source>This is your own SimpleX address!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This is your own one-time link!" xml:space="preserve">
|
||||
<source>This is your own one-time link!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This setting applies to messages in your current chat profile **%@**." xml:space="preserve">
|
||||
<source>This setting applies to messages in your current chat profile **%@**.</source>
|
||||
<target>Tämä asetus koskee nykyisen keskusteluprofiilisi viestejä *%@**.</target>
|
||||
@@ -4747,6 +5023,10 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.</t
|
||||
<target>Kontaktisi voi muodostaa yhteyden skannaamalla QR-koodin tai käyttämällä sovelluksessa olevaa linkkiä.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="To hide unwanted messages." xml:space="preserve">
|
||||
<source>To hide unwanted messages.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="To make a new connection" xml:space="preserve">
|
||||
<source>To make a new connection</source>
|
||||
<target>Uuden yhteyden luominen</target>
|
||||
@@ -4828,6 +5108,18 @@ Sinua kehotetaan suorittamaan todennus loppuun, ennen kuin tämä ominaisuus ote
|
||||
<target>Ääniviestiä ei voi tallentaa</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unblock" xml:space="preserve">
|
||||
<source>Unblock</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?" xml:space="preserve">
|
||||
<source>Unblock member?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unexpected error: %@" xml:space="preserve">
|
||||
<source>Unexpected error: %@</source>
|
||||
<target>Odottamaton virhe: %@</target>
|
||||
@@ -4890,6 +5182,14 @@ To connect, please ask your contact to create another connection link and check
|
||||
Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja tarkista, että verkkoyhteytesi on vakaa.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unlink" xml:space="preserve">
|
||||
<source>Unlink</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unlink desktop?" xml:space="preserve">
|
||||
<source>Unlink desktop?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unlock" xml:space="preserve">
|
||||
<source>Unlock</source>
|
||||
<target>Avaa</target>
|
||||
@@ -4980,6 +5280,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
|
||||
<target>Käytä uusiin yhteyksiin</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Use from desktop" xml:space="preserve">
|
||||
<source>Use from desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Use iOS call interface" xml:space="preserve">
|
||||
<source>Use iOS call interface</source>
|
||||
<target>Käytä iOS:n puhelujen käyttöliittymää</target>
|
||||
@@ -5010,11 +5314,23 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
|
||||
<target>Käyttää SimpleX Chat -palvelimia.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify code with desktop" xml:space="preserve">
|
||||
<source>Verify code with desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify connection" xml:space="preserve">
|
||||
<source>Verify connection</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify connection security" xml:space="preserve">
|
||||
<source>Verify connection security</source>
|
||||
<target>Tarkista yhteyden suojaus</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify connections" xml:space="preserve">
|
||||
<source>Verify connections</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify security code" xml:space="preserve">
|
||||
<source>Verify security code</source>
|
||||
<target>Tarkista turvakoodi</target>
|
||||
@@ -5025,6 +5341,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
|
||||
<target>Selaimella</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Via secure quantum resistant protocol." xml:space="preserve">
|
||||
<source>Via secure quantum resistant protocol.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Video call" xml:space="preserve">
|
||||
<source>Video call</source>
|
||||
<target>Videopuhelu</target>
|
||||
@@ -5075,6 +5395,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
|
||||
<target>Ääniviesti…</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Waiting for desktop..." xml:space="preserve">
|
||||
<source>Waiting for desktop...</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Waiting for file" xml:space="preserve">
|
||||
<source>Waiting for file</source>
|
||||
<target>Odottaa tiedostoa</target>
|
||||
@@ -5175,6 +5499,35 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
|
||||
<target>Olet jo muodostanut yhteyden %@:n kanssa.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are already connecting to %@." xml:space="preserve">
|
||||
<source>You are already connecting to %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are already connecting via this one-time link!" xml:space="preserve">
|
||||
<source>You are already connecting via this one-time link!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are already in group %@." xml:space="preserve">
|
||||
<source>You are already in group %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are already joining the group %@." xml:space="preserve">
|
||||
<source>You are already joining the group %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are already joining the group via this link!" xml:space="preserve">
|
||||
<source>You are already joining the group via this link!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are already joining the group via this link." xml:space="preserve">
|
||||
<source>You are already joining the group via this link.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are already joining the group! Repeat join request?" xml:space="preserve">
|
||||
<source>You are already joining the group!
|
||||
Repeat join request?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are connected to the server used to receive messages from this contact." xml:space="preserve">
|
||||
<source>You are connected to the server used to receive messages from this contact.</source>
|
||||
<target>Olet yhteydessä palvelimeen, jota käytetään vastaanottamaan viestejä tältä kontaktilta.</target>
|
||||
@@ -5270,6 +5623,15 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
|
||||
<target>Sinua ei voitu todentaa; yritä uudelleen.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You have already requested connection via this address!" xml:space="preserve">
|
||||
<source>You have already requested connection via this address!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You have already requested connection! Repeat connection request?" xml:space="preserve">
|
||||
<source>You have already requested connection!
|
||||
Repeat connection request?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You have no chats" xml:space="preserve">
|
||||
<source>You have no chats</source>
|
||||
<target>Sinulla ei ole keskusteluja</target>
|
||||
@@ -5320,6 +5682,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
|
||||
<target>Sinut yhdistetään ryhmään, kun ryhmän isännän laite on online-tilassa, odota tai tarkista myöhemmin!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You will be connected when group link host's device is online, please wait or check later!" xml:space="preserve">
|
||||
<source>You will be connected when group link host's device is online, please wait or check later!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You will be connected when your connection request is accepted, please wait or check later!" xml:space="preserve">
|
||||
<source>You will be connected when your connection request is accepted, please wait or check later!</source>
|
||||
<target>Sinut yhdistetään, kun yhteyspyyntösi on hyväksytty, odota tai tarkista myöhemmin!</target>
|
||||
@@ -5335,9 +5701,8 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
|
||||
<target>Sinun on tunnistauduttava, kun käynnistät sovelluksen tai jatkat sen käyttöä 30 sekunnin tauon jälkeen.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You will join a group this link refers to and connect to its group members." xml:space="preserve">
|
||||
<source>You will join a group this link refers to and connect to its group members.</source>
|
||||
<target>Liityt ryhmään, johon tämä linkki viittaa, ja muodostat yhteyden sen ryhmän jäseniin.</target>
|
||||
<trans-unit id="You will connect to all group members." xml:space="preserve">
|
||||
<source>You will connect to all group members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You will still receive calls and notifications from muted profiles when they are active." xml:space="preserve">
|
||||
@@ -5405,11 +5770,6 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja
|
||||
<target>Keskustelut-tietokantasi ei ole salattu - aseta tunnuslause sen salaamiseksi.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your chat profile will be sent to group members" xml:space="preserve">
|
||||
<source>Your chat profile will be sent to group members</source>
|
||||
<target>Keskusteluprofiilisi lähetetään ryhmän jäsenille</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your chat profiles" xml:space="preserve">
|
||||
<source>Your chat profiles</source>
|
||||
<target>Keskusteluprofiilisi</target>
|
||||
@@ -5464,6 +5824,10 @@ Voit muuttaa sitä Asetuksista.</target>
|
||||
<target>Yksityisyytesi</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your profile" xml:space="preserve">
|
||||
<source>Your profile</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your profile **%@** will be shared." xml:space="preserve">
|
||||
<source>Your profile **%@** will be shared.</source>
|
||||
<target>Profiilisi **%@** jaetaan.</target>
|
||||
@@ -5556,11 +5920,19 @@ SimpleX-palvelimet eivät näe profiiliasi.</target>
|
||||
<target>aina</target>
|
||||
<note>pref value</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="and %lld other events" xml:space="preserve">
|
||||
<source>and %lld other events</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="audio call (not e2e encrypted)" xml:space="preserve">
|
||||
<source>audio call (not e2e encrypted)</source>
|
||||
<target>äänipuhelu (ei e2e-salattu)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="author" xml:space="preserve">
|
||||
<source>author</source>
|
||||
<note>member role</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="bad message ID" xml:space="preserve">
|
||||
<source>bad message ID</source>
|
||||
<target>virheellinen viestin tunniste</target>
|
||||
@@ -5571,6 +5943,10 @@ SimpleX-palvelimet eivät näe profiiliasi.</target>
|
||||
<target>virheellinen viestin tarkiste</target>
|
||||
<note>integrity error chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="blocked" xml:space="preserve">
|
||||
<source>blocked</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="bold" xml:space="preserve">
|
||||
<source>bold</source>
|
||||
<target>lihavoitu</target>
|
||||
@@ -5740,6 +6116,10 @@ SimpleX-palvelimet eivät näe profiiliasi.</target>
|
||||
<target>poistettu</target>
|
||||
<note>deleted chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="deleted contact" xml:space="preserve">
|
||||
<source>deleted contact</source>
|
||||
<note>rcv direct event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="deleted group" xml:space="preserve">
|
||||
<source>deleted group</source>
|
||||
<target>poistettu ryhmä</target>
|
||||
@@ -6024,7 +6404,8 @@ SimpleX-palvelimet eivät näe profiiliasi.</target>
|
||||
<source>off</source>
|
||||
<target>pois</target>
|
||||
<note>enabled status
|
||||
group pref value</note>
|
||||
group pref value
|
||||
time to disappear</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="offered %@" xml:space="preserve">
|
||||
<source>offered %@</source>
|
||||
@@ -6041,11 +6422,6 @@ SimpleX-palvelimet eivät näe profiiliasi.</target>
|
||||
<target>päällä</target>
|
||||
<note>group pref value</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="or chat with the developers" xml:space="preserve">
|
||||
<source>or chat with the developers</source>
|
||||
<target>tai keskustele kehittäjien kanssa</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="owner" xml:space="preserve">
|
||||
<source>owner</source>
|
||||
<target>omistaja</target>
|
||||
@@ -6135,6 +6511,10 @@ SimpleX-palvelimet eivät näe profiiliasi.</target>
|
||||
<target>päivitetty ryhmäprofiili</target>
|
||||
<note>rcv group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="v%@" xml:space="preserve">
|
||||
<source>v%@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="v%@ (%@)" xml:space="preserve">
|
||||
<source>v%@ (%@)</source>
|
||||
<target>v%@ (%@)</target>
|
||||
@@ -6272,6 +6652,10 @@ SimpleX-palvelimet eivät näe profiiliasi.</target>
|
||||
<target>SimpleX käyttää Face ID:tä paikalliseen todennukseen</target>
|
||||
<note>Privacy - Face ID Usage Description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="NSLocalNetworkUsageDescription" xml:space="preserve">
|
||||
<source>SimpleX uses local network access to allow using user chat profile via desktop app on the same network.</source>
|
||||
<note>Privacy - Local Network Usage Description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="NSMicrophoneUsageDescription" xml:space="preserve">
|
||||
<source>SimpleX needs microphone access for audio and video calls, and to record voice messages.</source>
|
||||
<target>SimpleX tarvitsee mikrofonia ääni- ja videopuheluita ja ääniviestien tallentamista varten.</target>
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
"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 */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,8 @@
|
||||
"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 */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,8 @@
|
||||
"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 */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,8 @@
|
||||
"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 */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,8 @@
|
||||
"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 */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,8 @@
|
||||
"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 */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,8 @@
|
||||
"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 */
|
||||
|
||||
@@ -84,6 +84,10 @@
|
||||
<target>%@ / %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ and %@" xml:space="preserve">
|
||||
<source>%@ and %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ and %@ connected" xml:space="preserve">
|
||||
<source>%@ and %@ connected</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -93,6 +97,10 @@
|
||||
<target>%1$@ ที่ %2$@:</target>
|
||||
<note>copied message info, <sender> at <time></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ connected" xml:space="preserve">
|
||||
<source>%@ connected</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ is connected!" xml:space="preserve">
|
||||
<source>%@ is connected!</source>
|
||||
<target>%@ เชื่อมต่อสำเร็จ!</target>
|
||||
@@ -118,6 +126,10 @@
|
||||
<target>%@ อยากเชื่อมต่อ!</target>
|
||||
<note>notification title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@, %@ and %lld members" xml:space="preserve">
|
||||
<source>%@, %@ and %lld members</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@, %@ and %lld other members connected" xml:space="preserve">
|
||||
<source>%@, %@ and %lld other members connected</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -182,11 +194,27 @@
|
||||
<target>%lld ไฟล์ที่มีขนาดรวม %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld group events" xml:space="preserve">
|
||||
<source>%lld group events</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld members" xml:space="preserve">
|
||||
<source>%lld members</source>
|
||||
<target>%lld สมาชิก</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld messages blocked" xml:space="preserve">
|
||||
<source>%lld messages blocked</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>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld messages moderated by %@" xml:space="preserve">
|
||||
<source>%lld messages moderated by %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld minutes" xml:space="preserve">
|
||||
<source>%lld minutes</source>
|
||||
<target>%lld นาที</target>
|
||||
@@ -256,6 +284,14 @@
|
||||
<target>(</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="(new)" xml:space="preserve">
|
||||
<source>(new)</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="(this device v%@)" xml:space="preserve">
|
||||
<source>(this device v%@)</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id=")" xml:space="preserve">
|
||||
<source>)</source>
|
||||
<target>)</target>
|
||||
@@ -341,6 +377,12 @@
|
||||
- และอื่น ๆ!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="- optionally notify deleted contacts. - profile names with spaces. - and more!" xml:space="preserve">
|
||||
<source>- optionally notify deleted contacts.
|
||||
- profile names with spaces.
|
||||
- and more!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="- voice messages up to 5 minutes. - custom time to disappear. - editing history." xml:space="preserve">
|
||||
<source>- voice messages up to 5 minutes.
|
||||
- custom time to disappear.
|
||||
@@ -355,6 +397,10 @@
|
||||
<target>.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="0 sec" xml:space="preserve">
|
||||
<source>0 sec</source>
|
||||
<note>time to disappear</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="0s" xml:space="preserve">
|
||||
<source>0s</source>
|
||||
<target>0s</target>
|
||||
@@ -578,6 +624,10 @@
|
||||
<target>ข้อความทั้งหมดจะถูกลบ - ไม่สามารถยกเลิกได้! ข้อความจะถูกลบสำหรับคุณเท่านั้น.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All new messages from %@ will be hidden!" xml:space="preserve">
|
||||
<source>All new messages from %@ will be hidden!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All your contacts will remain connected." xml:space="preserve">
|
||||
<source>All your contacts will remain connected.</source>
|
||||
<target>ผู้ติดต่อทั้งหมดของคุณจะยังคงเชื่อมต่ออยู่.</target>
|
||||
@@ -683,6 +733,14 @@
|
||||
<target>เชื่อมต่อสำเร็จแล้ว?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Already connecting!" xml:space="preserve">
|
||||
<source>Already connecting!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Already joining the group!" xml:space="preserve">
|
||||
<source>Already joining the group!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Always use relay" xml:space="preserve">
|
||||
<source>Always use relay</source>
|
||||
<target>ใช้รีเลย์เสมอ</target>
|
||||
@@ -802,6 +860,10 @@
|
||||
<target>กลับ</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Bad desktop address" xml:space="preserve">
|
||||
<source>Bad desktop address</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Bad message ID" xml:space="preserve">
|
||||
<source>Bad message ID</source>
|
||||
<target>ID ข้อความที่ไม่ดี</target>
|
||||
@@ -812,11 +874,31 @@
|
||||
<target>แฮชข้อความไม่ดี</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better groups" xml:space="preserve">
|
||||
<source>Better groups</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better messages" xml:space="preserve">
|
||||
<source>Better messages</source>
|
||||
<target>ข้อความที่ดีขึ้น</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Block" xml:space="preserve">
|
||||
<source>Block</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>
|
||||
</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="Block member?" xml:space="preserve">
|
||||
<source>Block member?</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>
|
||||
@@ -1077,21 +1159,34 @@
|
||||
<target>เชื่อมต่อ</target>
|
||||
<note>server test step</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect directly" xml:space="preserve">
|
||||
<source>Connect directly</source>
|
||||
<trans-unit id="Connect automatically" xml:space="preserve">
|
||||
<source>Connect automatically</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect incognito" xml:space="preserve">
|
||||
<source>Connect incognito</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via contact link" xml:space="preserve">
|
||||
<source>Connect via contact link</source>
|
||||
<trans-unit id="Connect to desktop" xml:space="preserve">
|
||||
<source>Connect to desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via group link?" xml:space="preserve">
|
||||
<source>Connect via group link?</source>
|
||||
<target>เชื่อมต่อผ่านลิงค์กลุ่ม?</target>
|
||||
<trans-unit id="Connect to yourself?" xml:space="preserve">
|
||||
<source>Connect to yourself?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect to yourself? This is your own SimpleX address!" xml:space="preserve">
|
||||
<source>Connect to yourself?
|
||||
This is your own SimpleX address!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect to yourself? This is your own one-time link!" xml:space="preserve">
|
||||
<source>Connect to yourself?
|
||||
This is your own one-time link!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via contact address" xml:space="preserve">
|
||||
<source>Connect via contact address</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via link" xml:space="preserve">
|
||||
@@ -1108,6 +1203,18 @@
|
||||
<source>Connect via one-time link</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect with %@" xml:space="preserve">
|
||||
<source>Connect with %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connected desktop" xml:space="preserve">
|
||||
<source>Connected desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connected to desktop" xml:space="preserve">
|
||||
<source>Connected to desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connecting server…" xml:space="preserve">
|
||||
<source>Connecting to server…</source>
|
||||
<target>กำลังเชื่อมต่อกับเซิร์ฟเวอร์…</target>
|
||||
@@ -1118,6 +1225,10 @@
|
||||
<target>กำลังเชื่อมต่อกับเซิร์ฟเวอร์... (ข้อผิดพลาด: %@)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connecting to desktop" xml:space="preserve">
|
||||
<source>Connecting to desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection" xml:space="preserve">
|
||||
<source>Connection</source>
|
||||
<target>การเชื่อมต่อ</target>
|
||||
@@ -1138,6 +1249,10 @@
|
||||
<target>ส่งคําขอเชื่อมต่อแล้ว!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection terminated" xml:space="preserve">
|
||||
<source>Connection terminated</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection timeout" xml:space="preserve">
|
||||
<source>Connection timeout</source>
|
||||
<target>หมดเวลาการเชื่อมต่อ</target>
|
||||
@@ -1153,11 +1268,6 @@
|
||||
<target>ผู้ติดต่อรายนี้มีอยู่แล้ว</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Contact and all messages will be deleted - this cannot be undone!" xml:space="preserve">
|
||||
<source>Contact and all messages will be deleted - this cannot be undone!</source>
|
||||
<target>ผู้ติดต่อและข้อความทั้งหมดจะถูกลบ - ไม่สามารถยกเลิกได้!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Contact hidden:" xml:space="preserve">
|
||||
<source>Contact hidden:</source>
|
||||
<target>ผู้ติดต่อถูกซ่อน:</target>
|
||||
@@ -1208,6 +1318,10 @@
|
||||
<target>รุ่นหลัก: v%@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Correct name to %@?" xml:space="preserve">
|
||||
<source>Correct name to %@?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create" xml:space="preserve">
|
||||
<source>Create</source>
|
||||
<target>สร้าง</target>
|
||||
@@ -1218,6 +1332,10 @@
|
||||
<target>สร้างที่อยู่ SimpleX</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create a group using a random profile." xml:space="preserve">
|
||||
<source>Create a group using a random profile.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create an address to let people connect with you." xml:space="preserve">
|
||||
<source>Create an address to let people connect with you.</source>
|
||||
<target>สร้างที่อยู่เพื่อให้ผู้อื่นเชื่อมต่อกับคุณ</target>
|
||||
@@ -1228,6 +1346,10 @@
|
||||
<target>สร้างไฟล์</target>
|
||||
<note>server test step</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create group" xml:space="preserve">
|
||||
<source>Create group</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create group link" xml:space="preserve">
|
||||
<source>Create group link</source>
|
||||
<target>สร้างลิงค์กลุ่ม</target>
|
||||
@@ -1247,6 +1369,10 @@
|
||||
<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>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create queue" xml:space="preserve">
|
||||
<source>Create queue</source>
|
||||
<target>สร้างคิว</target>
|
||||
@@ -1405,6 +1531,10 @@
|
||||
<target>ลบ</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete %lld messages?" xml:space="preserve">
|
||||
<source>Delete %lld messages?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete Contact" xml:space="preserve">
|
||||
<source>Delete Contact</source>
|
||||
<target>ลบผู้ติดต่อ</target>
|
||||
@@ -1430,6 +1560,10 @@
|
||||
<target>ลบไฟล์ทั้งหมด</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete and notify contact" xml:space="preserve">
|
||||
<source>Delete and notify contact</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete archive" xml:space="preserve">
|
||||
<source>Delete archive</source>
|
||||
<target>ลบที่เก็บถาวร</target>
|
||||
@@ -1460,9 +1594,9 @@
|
||||
<target>ลบผู้ติดต่อ</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete contact?" xml:space="preserve">
|
||||
<source>Delete contact?</source>
|
||||
<target>ลบผู้ติดต่อ?</target>
|
||||
<trans-unit id="Delete contact? This cannot be undone!" xml:space="preserve">
|
||||
<source>Delete contact?
|
||||
This cannot be undone!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete database" xml:space="preserve">
|
||||
@@ -1604,6 +1738,18 @@
|
||||
<target>คำอธิบาย</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Desktop address" xml:space="preserve">
|
||||
<source>Desktop address</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Desktop app version %@ is not compatible with this app." xml:space="preserve">
|
||||
<source>Desktop app version %@ is not compatible with this app.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Desktop devices" xml:space="preserve">
|
||||
<source>Desktop devices</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Develop" xml:space="preserve">
|
||||
<source>Develop</source>
|
||||
<target>พัฒนา</target>
|
||||
@@ -1694,18 +1840,16 @@
|
||||
<target>ตัดการเชื่อมต่อ</target>
|
||||
<note>server test step</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disconnect desktop?" xml:space="preserve">
|
||||
<source>Disconnect desktop?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Discover and join groups" xml:space="preserve">
|
||||
<source>Discover and join groups</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Display name" xml:space="preserve">
|
||||
<source>Display name</source>
|
||||
<target>ชื่อที่แสดง</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Display name:" xml:space="preserve">
|
||||
<source>Display name:</source>
|
||||
<target>ชื่อที่แสดง:</target>
|
||||
<trans-unit id="Discover via local network" xml:space="preserve">
|
||||
<source>Discover via local network</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
|
||||
@@ -1876,6 +2020,14 @@
|
||||
<target>ข้อความที่ encrypt: ข้อผิดพลาดที่ไม่คาดคิด</target>
|
||||
<note>notification</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Encryption re-negotiation error" xml:space="preserve">
|
||||
<source>Encryption re-negotiation error</source>
|
||||
<note>message decrypt error item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Encryption re-negotiation failed." xml:space="preserve">
|
||||
<source>Encryption re-negotiation failed.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter Passcode" xml:space="preserve">
|
||||
<source>Enter Passcode</source>
|
||||
<target>ใส่รหัสผ่าน</target>
|
||||
@@ -1886,6 +2038,10 @@
|
||||
<target>ใส่รหัสผ่านที่ถูกต้อง</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter group name…" xml:space="preserve">
|
||||
<source>Enter group name…</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter passphrase…" xml:space="preserve">
|
||||
<source>Enter passphrase…</source>
|
||||
<target>ใส่รหัสผ่าน</target>
|
||||
@@ -1901,6 +2057,10 @@
|
||||
<target>ใส่เซิร์ฟเวอร์ด้วยตนเอง</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter this device name…" xml:space="preserve">
|
||||
<source>Enter this device name…</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter welcome message…" xml:space="preserve">
|
||||
<source>Enter welcome message…</source>
|
||||
<target>ใส่ข้อความต้อนรับ…</target>
|
||||
@@ -1911,6 +2071,10 @@
|
||||
<target>ใส่ข้อความต้อนรับ… (ไม่บังคับ)</target>
|
||||
<note>placeholder</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter your name…" xml:space="preserve">
|
||||
<source>Enter your name…</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error" xml:space="preserve">
|
||||
<source>Error</source>
|
||||
<target>ผิดพลาด</target>
|
||||
@@ -2183,6 +2347,10 @@
|
||||
<target>ออกโดยไม่บันทึก</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Expand" xml:space="preserve">
|
||||
<source>Expand</source>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Export database" xml:space="preserve">
|
||||
<source>Export database</source>
|
||||
<target>ส่งออกฐานข้อมูล</target>
|
||||
@@ -2213,6 +2381,10 @@
|
||||
<target>รวดเร็วและไม่ต้องรอจนกว่าผู้ส่งจะออนไลน์!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Faster joining and more reliable messages." xml:space="preserve">
|
||||
<source>Faster joining and more reliable messages.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Favorite" xml:space="preserve">
|
||||
<source>Favorite</source>
|
||||
<target>ที่ชอบ</target>
|
||||
@@ -2308,6 +2480,10 @@
|
||||
<target>สำหรับคอนโซล</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Found desktop" xml:space="preserve">
|
||||
<source>Found desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="French interface" xml:space="preserve">
|
||||
<source>French interface</source>
|
||||
<target>อินเทอร์เฟซภาษาฝรั่งเศส</target>
|
||||
@@ -2328,6 +2504,10 @@
|
||||
<target>ชื่อเต็ม:</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Fully decentralized – visible only to members." xml:space="preserve">
|
||||
<source>Fully decentralized – visible only to members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Fully re-implemented - work in background!" xml:space="preserve">
|
||||
<source>Fully re-implemented - work in background!</source>
|
||||
<target>ดำเนินการใหม่อย่างสมบูรณ์ - ทำงานในพื้นหลัง!</target>
|
||||
@@ -2348,6 +2528,14 @@
|
||||
<target>กลุ่ม</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group already exists" xml:space="preserve">
|
||||
<source>Group already exists</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group already exists!" xml:space="preserve">
|
||||
<source>Group already exists!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group display name" xml:space="preserve">
|
||||
<source>Group display name</source>
|
||||
<target>ชื่อกลุ่มที่แสดง</target>
|
||||
@@ -2618,6 +2806,10 @@
|
||||
<target>ไม่ระบุตัวตน</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incognito groups" xml:space="preserve">
|
||||
<source>Incognito groups</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incognito mode" xml:space="preserve">
|
||||
<source>Incognito mode</source>
|
||||
<target>โหมดไม่ระบุตัวตน</target>
|
||||
@@ -2647,6 +2839,10 @@
|
||||
<target>เวอร์ชันฐานข้อมูลที่เข้ากันไม่ได้</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incompatible version" xml:space="preserve">
|
||||
<source>Incompatible version</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incorrect passcode" xml:space="preserve">
|
||||
<source>Incorrect passcode</source>
|
||||
<target>รหัสผ่านไม่ถูกต้อง</target>
|
||||
@@ -2694,6 +2890,10 @@
|
||||
<target>ลิงค์เชื่อมต่อไม่ถูกต้อง</target>
|
||||
<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 server address!" xml:space="preserve">
|
||||
<source>Invalid server address!</source>
|
||||
<target>ที่อยู่เซิร์ฟเวอร์ไม่ถูกต้อง!</target>
|
||||
@@ -2784,16 +2984,33 @@
|
||||
<target>เข้าร่วมกลุ่ม</target>
|
||||
<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>
|
||||
</trans-unit>
|
||||
<trans-unit id="Join incognito" xml:space="preserve">
|
||||
<source>Join incognito</source>
|
||||
<target>เข้าร่วมแบบไม่ระบุตัวตน</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Join with current profile" xml:space="preserve">
|
||||
<source>Join with current profile</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Join your group? This is your link for group %@!" xml:space="preserve">
|
||||
<source>Join your group?
|
||||
This is your link for group %@!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Joining group" xml:space="preserve">
|
||||
<source>Joining group</source>
|
||||
<target>กำลังจะเข้าร่วมกลุ่ม</target>
|
||||
<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 your connections" xml:space="preserve">
|
||||
<source>Keep your connections</source>
|
||||
<target>รักษาการเชื่อมต่อของคุณ</target>
|
||||
@@ -2854,6 +3071,18 @@
|
||||
<target>ข้อจำกัด</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Link mobile and desktop apps! 🔗" xml:space="preserve">
|
||||
<source>Link mobile and desktop apps! 🔗</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Linked desktop options" xml:space="preserve">
|
||||
<source>Linked desktop options</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Linked desktops" xml:space="preserve">
|
||||
<source>Linked desktops</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Live message!" xml:space="preserve">
|
||||
<source>Live message!</source>
|
||||
<target>ข้อความสด!</target>
|
||||
@@ -3004,6 +3233,10 @@
|
||||
<target>ข้อความและไฟล์</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Messages from %@ will be shown!" xml:space="preserve">
|
||||
<source>Messages from %@ will be shown!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrating database archive…" xml:space="preserve">
|
||||
<source>Migrating database archive…</source>
|
||||
<target>กำลังย้ายข้อมูลที่เก็บถาวรของฐานข้อมูล…</target>
|
||||
@@ -3196,6 +3429,10 @@
|
||||
<target>ไม่มีไฟล์ที่ได้รับหรือส่ง</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Not compatible!" xml:space="preserve">
|
||||
<source>Not compatible!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Notifications" xml:space="preserve">
|
||||
<source>Notifications</source>
|
||||
<target>การแจ้งเตือน</target>
|
||||
@@ -3349,6 +3586,10 @@
|
||||
<target>เปิดคอนโซลการแชท</target>
|
||||
<note>authentication reason</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open group" xml:space="preserve">
|
||||
<source>Open group</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open user profiles" xml:space="preserve">
|
||||
<source>Open user profiles</source>
|
||||
<target>เปิดโปรไฟล์ผู้ใช้</target>
|
||||
@@ -3364,11 +3605,6 @@
|
||||
<target>กำลังเปิดฐานข้อมูล…</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red." xml:space="preserve">
|
||||
<source>Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red.</source>
|
||||
<target>การเปิดลิงก์ในเบราว์เซอร์อาจลดความเป็นส่วนตัวและความปลอดภัยของการเชื่อมต่อ ลิงก์ SimpleX ที่ไม่น่าเชื่อถือจะเป็นสีแดง</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="PING count" xml:space="preserve">
|
||||
<source>PING count</source>
|
||||
<target>จํานวน PING</target>
|
||||
@@ -3414,6 +3650,10 @@
|
||||
<target>แปะ</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste desktop address" xml:space="preserve">
|
||||
<source>Paste desktop address</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste image" xml:space="preserve">
|
||||
<source>Paste image</source>
|
||||
<target>แปะภาพ</target>
|
||||
@@ -3558,6 +3798,14 @@
|
||||
<target>รูปโปรไฟล์</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Profile name" xml:space="preserve">
|
||||
<source>Profile name</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Profile name:" xml:space="preserve">
|
||||
<source>Profile name:</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Profile password" xml:space="preserve">
|
||||
<source>Profile password</source>
|
||||
<target>รหัสผ่านโปรไฟล์</target>
|
||||
@@ -3801,6 +4049,14 @@
|
||||
<target>เจรจา enryption ใหม่หรือไม่?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Repeat connection request?" xml:space="preserve">
|
||||
<source>Repeat connection request?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Repeat join request?" xml:space="preserve">
|
||||
<source>Repeat join request?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reply" xml:space="preserve">
|
||||
<source>Reply</source>
|
||||
<target>ตอบ</target>
|
||||
@@ -3986,6 +4242,10 @@
|
||||
<target>สแกนคิวอาร์โค้ด</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Scan QR code from desktop" xml:space="preserve">
|
||||
<source>Scan QR code from desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Scan code" xml:space="preserve">
|
||||
<source>Scan code</source>
|
||||
<target>สแกนรหัส</target>
|
||||
@@ -4203,6 +4463,10 @@
|
||||
<target>เซิร์ฟเวอร์</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Session code" xml:space="preserve">
|
||||
<source>Session code</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Set 1 day" xml:space="preserve">
|
||||
<source>Set 1 day</source>
|
||||
<target>ตั้ง 1 วัน</target>
|
||||
@@ -4510,6 +4774,10 @@
|
||||
<target>แตะปุ่ม </target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to Connect" xml:space="preserve">
|
||||
<source>Tap to Connect</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to activate profile." xml:space="preserve">
|
||||
<source>Tap to activate profile.</source>
|
||||
<target>แตะเพื่อเปิดใช้งานโปรไฟล์</target>
|
||||
@@ -4608,11 +4876,6 @@ It can happen because of some bug or when the connection is compromised.</source
|
||||
<target>encryption กำลังทำงานและไม่จำเป็นต้องใช้ข้อตกลง encryption ใหม่ อาจทำให้การเชื่อมต่อผิดพลาดได้!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The group is fully decentralized – it is visible only to the members." xml:space="preserve">
|
||||
<source>The group is fully decentralized – it is visible only to the members.</source>
|
||||
<target>กลุ่มมีการกระจายอำนาจอย่างเต็มที่ – มองเห็นได้เฉพาะสมาชิกเท่านั้น</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The hash of the previous message is different." xml:space="preserve">
|
||||
<source>The hash of the previous message is different.</source>
|
||||
<target>แฮชของข้อความก่อนหน้านี้แตกต่างกัน</target>
|
||||
@@ -4697,6 +4960,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="This device name" xml:space="preserve">
|
||||
<source>This device 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>
|
||||
@@ -4706,6 +4973,14 @@ 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="This is your own SimpleX address!" xml:space="preserve">
|
||||
<source>This is your own SimpleX address!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This is your own one-time link!" xml:space="preserve">
|
||||
<source>This is your own one-time link!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This setting applies to messages in your current chat profile **%@**." xml:space="preserve">
|
||||
<source>This setting applies to messages in your current chat profile **%@**.</source>
|
||||
<target>การตั้งค่านี้ใช้กับข้อความในโปรไฟล์แชทปัจจุบันของคุณ **%@**</target>
|
||||
@@ -4721,6 +4996,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="To hide unwanted messages." xml:space="preserve">
|
||||
<source>To hide unwanted messages.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="To make a new connection" xml:space="preserve">
|
||||
<source>To make a new connection</source>
|
||||
<target>เพื่อสร้างการเชื่อมต่อใหม่</target>
|
||||
@@ -4802,6 +5081,18 @@ 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="Unblock" xml:space="preserve">
|
||||
<source>Unblock</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?" xml:space="preserve">
|
||||
<source>Unblock member?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unexpected error: %@" xml:space="preserve">
|
||||
<source>Unexpected error: %@</source>
|
||||
<target>ข้อผิดพลาดที่ไม่คาดคิด: %@</target>
|
||||
@@ -4864,6 +5155,14 @@ To connect, please ask your contact to create another connection link and check
|
||||
ในการเชื่อมต่อ โปรดขอให้ผู้ติดต่อของคุณสร้างลิงก์การเชื่อมต่ออื่น และตรวจสอบว่าคุณมีการเชื่อมต่อเครือข่ายที่เสถียร</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unlink" xml:space="preserve">
|
||||
<source>Unlink</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unlink desktop?" xml:space="preserve">
|
||||
<source>Unlink desktop?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unlock" xml:space="preserve">
|
||||
<source>Unlock</source>
|
||||
<target>ปลดล็อค</target>
|
||||
@@ -4953,6 +5252,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 from desktop" xml:space="preserve">
|
||||
<source>Use from desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Use iOS call interface" xml:space="preserve">
|
||||
<source>Use iOS call interface</source>
|
||||
<target>ใช้อินเทอร์เฟซการโทร iOS</target>
|
||||
@@ -4982,11 +5285,23 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>กำลังใช้เซิร์ฟเวอร์ SimpleX Chat อยู่</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify code with desktop" xml:space="preserve">
|
||||
<source>Verify code with desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify connection" xml:space="preserve">
|
||||
<source>Verify connection</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify connection security" xml:space="preserve">
|
||||
<source>Verify connection security</source>
|
||||
<target>ตรวจสอบความปลอดภัยในการเชื่อมต่อ</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify connections" xml:space="preserve">
|
||||
<source>Verify connections</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify security code" xml:space="preserve">
|
||||
<source>Verify security code</source>
|
||||
<target>ตรวจสอบรหัสความปลอดภัย</target>
|
||||
@@ -4997,6 +5312,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="Via secure quantum resistant protocol." xml:space="preserve">
|
||||
<source>Via secure quantum resistant protocol.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Video call" xml:space="preserve">
|
||||
<source>Video call</source>
|
||||
<target>การสนทนาทางวิดีโอ</target>
|
||||
@@ -5047,6 +5366,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="Waiting for desktop..." xml:space="preserve">
|
||||
<source>Waiting for desktop...</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Waiting for file" xml:space="preserve">
|
||||
<source>Waiting for file</source>
|
||||
<target>กำลังรอไฟล์</target>
|
||||
@@ -5147,6 +5470,35 @@ 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="You are already connecting to %@." xml:space="preserve">
|
||||
<source>You are already connecting to %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are already connecting via this one-time link!" xml:space="preserve">
|
||||
<source>You are already connecting via this one-time link!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are already in group %@." xml:space="preserve">
|
||||
<source>You are already in group %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are already joining the group %@." xml:space="preserve">
|
||||
<source>You are already joining the group %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are already joining the group via this link!" xml:space="preserve">
|
||||
<source>You are already joining the group via this link!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are already joining the group via this link." xml:space="preserve">
|
||||
<source>You are already joining the group via this link.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are already joining the group! Repeat join request?" xml:space="preserve">
|
||||
<source>You are already joining the group!
|
||||
Repeat join request?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are connected to the server used to receive messages from this contact." xml:space="preserve">
|
||||
<source>You are connected to the server used to receive messages from this contact.</source>
|
||||
<target>คุณเชื่อมต่อกับเซิร์ฟเวอร์ที่ใช้รับข้อความจากผู้ติดต่อนี้</target>
|
||||
@@ -5242,6 +5594,15 @@ 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="You have already requested connection via this address!" xml:space="preserve">
|
||||
<source>You have already requested connection via this address!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You have already requested connection! Repeat connection request?" xml:space="preserve">
|
||||
<source>You have already requested connection!
|
||||
Repeat connection request?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You have no chats" xml:space="preserve">
|
||||
<source>You have no chats</source>
|
||||
<target>คุณไม่มีการแชท</target>
|
||||
@@ -5291,6 +5652,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="You will be connected when group link host's device is online, please wait or check later!" xml:space="preserve">
|
||||
<source>You will be connected when group link host's device is online, please wait or check later!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You will be connected when your connection request is accepted, please wait or check later!" xml:space="preserve">
|
||||
<source>You will be connected when your connection request is accepted, please wait or check later!</source>
|
||||
<target>คุณจะเชื่อมต่อเมื่อคำขอเชื่อมต่อของคุณได้รับการยอมรับ โปรดรอหรือตรวจสอบในภายหลัง!</target>
|
||||
@@ -5306,9 +5671,8 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>คุณจะต้องตรวจสอบสิทธิ์เมื่อคุณเริ่มหรือกลับมาใช้แอปพลิเคชันอีกครั้งหลังจากผ่านไป 30 วินาทีในพื้นหลัง</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You will join a group this link refers to and connect to its group members." xml:space="preserve">
|
||||
<source>You will join a group this link refers to and connect to its group members.</source>
|
||||
<target>คุณจะเข้าร่วมกลุ่มที่ลิงก์นี้อ้างถึงและเชื่อมต่อกับสมาชิกในกลุ่ม</target>
|
||||
<trans-unit id="You will connect to all group members." xml:space="preserve">
|
||||
<source>You will connect to all group members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You will still receive calls and notifications from muted profiles when they are active." xml:space="preserve">
|
||||
@@ -5376,11 +5740,6 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>ฐานข้อมูลการแชทของคุณไม่ได้ถูก encrypt - ตั้งรหัสผ่านเพื่อ encrypt</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your chat profile will be sent to group members" xml:space="preserve">
|
||||
<source>Your chat profile will be sent to group members</source>
|
||||
<target>โปรไฟล์การแชทของคุณจะถูกส่งไปยังสมาชิกในกลุ่ม</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your chat profiles" xml:space="preserve">
|
||||
<source>Your chat profiles</source>
|
||||
<target>โปรไฟล์แชทของคุณ</target>
|
||||
@@ -5435,6 +5794,10 @@ You can change it in Settings.</source>
|
||||
<target>ความเป็นส่วนตัวของคุณ</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your profile" xml:space="preserve">
|
||||
<source>Your profile</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your profile **%@** will be shared." xml:space="preserve">
|
||||
<source>Your profile **%@** will be shared.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
@@ -5526,11 +5889,19 @@ SimpleX servers cannot see your profile.</source>
|
||||
<target>เสมอ</target>
|
||||
<note>pref value</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="and %lld other events" xml:space="preserve">
|
||||
<source>and %lld other events</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="audio call (not e2e encrypted)" xml:space="preserve">
|
||||
<source>audio call (not e2e encrypted)</source>
|
||||
<target>การโทรด้วยเสียง (ไม่ได้ encrypt จากต้นจนจบ)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="author" xml:space="preserve">
|
||||
<source>author</source>
|
||||
<note>member role</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="bad message ID" xml:space="preserve">
|
||||
<source>bad message ID</source>
|
||||
<target>ID ข้อความที่ไม่ดี</target>
|
||||
@@ -5541,6 +5912,10 @@ SimpleX servers cannot see your profile.</source>
|
||||
<target>แฮชข้อความไม่ดี</target>
|
||||
<note>integrity error chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="blocked" xml:space="preserve">
|
||||
<source>blocked</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="bold" xml:space="preserve">
|
||||
<source>bold</source>
|
||||
<target>ตัวหนา</target>
|
||||
@@ -5710,6 +6085,10 @@ SimpleX servers cannot see your profile.</source>
|
||||
<target>ลบแล้ว</target>
|
||||
<note>deleted chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="deleted contact" xml:space="preserve">
|
||||
<source>deleted contact</source>
|
||||
<note>rcv direct event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="deleted group" xml:space="preserve">
|
||||
<source>deleted group</source>
|
||||
<target>กลุ่มที่ถูกลบ</target>
|
||||
@@ -5992,7 +6371,8 @@ SimpleX servers cannot see your profile.</source>
|
||||
<source>off</source>
|
||||
<target>ปิด</target>
|
||||
<note>enabled status
|
||||
group pref value</note>
|
||||
group pref value
|
||||
time to disappear</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="offered %@" xml:space="preserve">
|
||||
<source>offered %@</source>
|
||||
@@ -6009,11 +6389,6 @@ SimpleX servers cannot see your profile.</source>
|
||||
<target>เปิด</target>
|
||||
<note>group pref value</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="or chat with the developers" xml:space="preserve">
|
||||
<source>or chat with the developers</source>
|
||||
<target>หรือแชทกับนักพัฒนาแอป</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="owner" xml:space="preserve">
|
||||
<source>owner</source>
|
||||
<target>เจ้าของ</target>
|
||||
@@ -6103,6 +6478,10 @@ SimpleX servers cannot see your profile.</source>
|
||||
<target>อัปเดตโปรไฟล์กลุ่มแล้ว</target>
|
||||
<note>rcv group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="v%@" xml:space="preserve">
|
||||
<source>v%@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="v%@ (%@)" xml:space="preserve">
|
||||
<source>v%@ (%@)</source>
|
||||
<target>v%@ (%@)</target>
|
||||
@@ -6240,6 +6619,10 @@ SimpleX servers cannot see your profile.</source>
|
||||
<target>SimpleX ใช้ Face ID สำหรับการรับรองความถูกต้องในเครื่อง</target>
|
||||
<note>Privacy - Face ID Usage Description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="NSLocalNetworkUsageDescription" xml:space="preserve">
|
||||
<source>SimpleX uses local network access to allow using user chat profile via desktop app on the same network.</source>
|
||||
<note>Privacy - Local Network Usage Description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="NSMicrophoneUsageDescription" xml:space="preserve">
|
||||
<source>SimpleX needs microphone access for audio and video calls, and to record voice messages.</source>
|
||||
<target>SimpleX ต้องการการเข้าถึงไมโครโฟนสำหรับการโทรด้วยเสียงและวิดีโอ และเพื่อบันทึกข้อความเสียง</target>
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
"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 */
|
||||
|
||||
@@ -87,6 +87,10 @@
|
||||
<target>%@ / %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ and %@" xml:space="preserve">
|
||||
<source>%@ and %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ and %@ connected" xml:space="preserve">
|
||||
<source>%@ and %@ connected</source>
|
||||
<target>%@ і %@ підключено</target>
|
||||
@@ -97,6 +101,10 @@
|
||||
<target>%1$@ за %2$@:</target>
|
||||
<note>copied message info, <sender> at <time></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ connected" xml:space="preserve">
|
||||
<source>%@ connected</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ is connected!" xml:space="preserve">
|
||||
<source>%@ is connected!</source>
|
||||
<target>%@ підключено!</target>
|
||||
@@ -122,6 +130,10 @@
|
||||
<target>%@ хоче підключитися!</target>
|
||||
<note>notification title</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@, %@ and %lld members" xml:space="preserve">
|
||||
<source>%@, %@ and %lld members</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@, %@ and %lld other members connected" xml:space="preserve">
|
||||
<source>%@, %@ and %lld other members connected</source>
|
||||
<target>%@, %@ та %lld інші підключені учасники</target>
|
||||
@@ -187,11 +199,27 @@
|
||||
<target>%lld файл(и) загальним розміром %@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld group events" xml:space="preserve">
|
||||
<source>%lld group events</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld members" xml:space="preserve">
|
||||
<source>%lld members</source>
|
||||
<target>%lld учасників</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld messages blocked" xml:space="preserve">
|
||||
<source>%lld messages blocked</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>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld messages moderated by %@" xml:space="preserve">
|
||||
<source>%lld messages moderated by %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld minutes" xml:space="preserve">
|
||||
<source>%lld minutes</source>
|
||||
<target>%lld хвилин</target>
|
||||
@@ -261,6 +289,14 @@
|
||||
<target>(</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="(new)" xml:space="preserve">
|
||||
<source>(new)</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="(this device v%@)" xml:space="preserve">
|
||||
<source>(this device v%@)</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id=")" xml:space="preserve">
|
||||
<source>)</source>
|
||||
<target>)</target>
|
||||
@@ -346,6 +382,12 @@
|
||||
- і багато іншого!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="- optionally notify deleted contacts. - profile names with spaces. - and more!" xml:space="preserve">
|
||||
<source>- optionally notify deleted contacts.
|
||||
- profile names with spaces.
|
||||
- and more!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="- voice messages up to 5 minutes. - custom time to disappear. - editing history." xml:space="preserve">
|
||||
<source>- voice messages up to 5 minutes.
|
||||
- custom time to disappear.
|
||||
@@ -360,6 +402,10 @@
|
||||
<target>.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="0 sec" xml:space="preserve">
|
||||
<source>0 sec</source>
|
||||
<note>time to disappear</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="0s" xml:space="preserve">
|
||||
<source>0s</source>
|
||||
<target>0с</target>
|
||||
@@ -585,6 +631,10 @@
|
||||
<target>Всі повідомлення будуть видалені - це неможливо скасувати! Повідомлення будуть видалені ТІЛЬКИ для вас.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All new messages from %@ will be hidden!" xml:space="preserve">
|
||||
<source>All new messages from %@ will be hidden!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All your contacts will remain connected." xml:space="preserve">
|
||||
<source>All your contacts will remain connected.</source>
|
||||
<target>Всі ваші контакти залишаться на зв'язку.</target>
|
||||
@@ -690,6 +740,14 @@
|
||||
<target>Вже підключено?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Already connecting!" xml:space="preserve">
|
||||
<source>Already connecting!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Already joining the group!" xml:space="preserve">
|
||||
<source>Already joining the group!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Always use relay" xml:space="preserve">
|
||||
<source>Always use relay</source>
|
||||
<target>Завжди використовуйте реле</target>
|
||||
@@ -809,6 +867,10 @@
|
||||
<target>Назад</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Bad desktop address" xml:space="preserve">
|
||||
<source>Bad desktop address</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Bad message ID" xml:space="preserve">
|
||||
<source>Bad message ID</source>
|
||||
<target>Неправильний ідентифікатор повідомлення</target>
|
||||
@@ -819,11 +881,31 @@
|
||||
<target>Поганий хеш повідомлення</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better groups" xml:space="preserve">
|
||||
<source>Better groups</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better messages" xml:space="preserve">
|
||||
<source>Better messages</source>
|
||||
<target>Кращі повідомлення</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Block" xml:space="preserve">
|
||||
<source>Block</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>
|
||||
</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="Block member?" xml:space="preserve">
|
||||
<source>Block member?</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>
|
||||
@@ -1084,9 +1166,8 @@
|
||||
<target>Підключіться</target>
|
||||
<note>server test step</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect directly" xml:space="preserve">
|
||||
<source>Connect directly</source>
|
||||
<target>Підключіться безпосередньо</target>
|
||||
<trans-unit id="Connect automatically" xml:space="preserve">
|
||||
<source>Connect automatically</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect incognito" xml:space="preserve">
|
||||
@@ -1094,14 +1175,26 @@
|
||||
<target>Підключайтеся інкогніто</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via contact link" xml:space="preserve">
|
||||
<source>Connect via contact link</source>
|
||||
<target>Підключіться за контактним посиланням</target>
|
||||
<trans-unit id="Connect to desktop" xml:space="preserve">
|
||||
<source>Connect to desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via group link?" xml:space="preserve">
|
||||
<source>Connect via group link?</source>
|
||||
<target>Підключитися за груповим посиланням?</target>
|
||||
<trans-unit id="Connect to yourself?" xml:space="preserve">
|
||||
<source>Connect to yourself?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect to yourself? This is your own SimpleX address!" xml:space="preserve">
|
||||
<source>Connect to yourself?
|
||||
This is your own SimpleX address!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect to yourself? This is your own one-time link!" xml:space="preserve">
|
||||
<source>Connect to yourself?
|
||||
This is your own one-time link!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via contact address" xml:space="preserve">
|
||||
<source>Connect via contact address</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect via link" xml:space="preserve">
|
||||
@@ -1119,6 +1212,18 @@
|
||||
<target>Під'єднатися за одноразовим посиланням</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connect with %@" xml:space="preserve">
|
||||
<source>Connect with %@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connected desktop" xml:space="preserve">
|
||||
<source>Connected desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connected to desktop" xml:space="preserve">
|
||||
<source>Connected to desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connecting server…" xml:space="preserve">
|
||||
<source>Connecting to server…</source>
|
||||
<target>Підключення до сервера…</target>
|
||||
@@ -1129,6 +1234,10 @@
|
||||
<target>Підключення до сервера... (помилка: %@)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connecting to desktop" xml:space="preserve">
|
||||
<source>Connecting to desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection" xml:space="preserve">
|
||||
<source>Connection</source>
|
||||
<target>Підключення</target>
|
||||
@@ -1149,6 +1258,10 @@
|
||||
<target>Запит на підключення відправлено!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection terminated" xml:space="preserve">
|
||||
<source>Connection terminated</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection timeout" xml:space="preserve">
|
||||
<source>Connection timeout</source>
|
||||
<target>Тайм-аут з'єднання</target>
|
||||
@@ -1164,11 +1277,6 @@
|
||||
<target>Контакт вже існує</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Contact and all messages will be deleted - this cannot be undone!" xml:space="preserve">
|
||||
<source>Contact and all messages will be deleted - this cannot be undone!</source>
|
||||
<target>Контакт і всі повідомлення будуть видалені - це неможливо скасувати!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Contact hidden:" xml:space="preserve">
|
||||
<source>Contact hidden:</source>
|
||||
<target>Контакт приховано:</target>
|
||||
@@ -1219,6 +1327,10 @@
|
||||
<target>Основна версія: v%@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Correct name to %@?" xml:space="preserve">
|
||||
<source>Correct name to %@?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create" xml:space="preserve">
|
||||
<source>Create</source>
|
||||
<target>Створити</target>
|
||||
@@ -1229,6 +1341,10 @@
|
||||
<target>Створіть адресу SimpleX</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create a group using a random profile." xml:space="preserve">
|
||||
<source>Create a group using a random profile.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create an address to let people connect with you." xml:space="preserve">
|
||||
<source>Create an address to let people connect with you.</source>
|
||||
<target>Створіть адресу, щоб люди могли з вами зв'язатися.</target>
|
||||
@@ -1239,6 +1355,10 @@
|
||||
<target>Створити файл</target>
|
||||
<note>server test step</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create group" xml:space="preserve">
|
||||
<source>Create group</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create group link" xml:space="preserve">
|
||||
<source>Create group link</source>
|
||||
<target>Створити групове посилання</target>
|
||||
@@ -1258,6 +1378,10 @@
|
||||
<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>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create queue" xml:space="preserve">
|
||||
<source>Create queue</source>
|
||||
<target>Створити чергу</target>
|
||||
@@ -1416,6 +1540,10 @@
|
||||
<target>Видалити</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete %lld messages?" xml:space="preserve">
|
||||
<source>Delete %lld messages?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete Contact" xml:space="preserve">
|
||||
<source>Delete Contact</source>
|
||||
<target>Видалити контакт</target>
|
||||
@@ -1441,6 +1569,10 @@
|
||||
<target>Видалити всі файли</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete and notify contact" xml:space="preserve">
|
||||
<source>Delete and notify contact</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete archive" xml:space="preserve">
|
||||
<source>Delete archive</source>
|
||||
<target>Видалити архів</target>
|
||||
@@ -1471,9 +1603,9 @@
|
||||
<target>Видалити контакт</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete contact?" xml:space="preserve">
|
||||
<source>Delete contact?</source>
|
||||
<target>Видалити контакт?</target>
|
||||
<trans-unit id="Delete contact? This cannot be undone!" xml:space="preserve">
|
||||
<source>Delete contact?
|
||||
This cannot be undone!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete database" xml:space="preserve">
|
||||
@@ -1616,6 +1748,18 @@
|
||||
<target>Опис</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Desktop address" xml:space="preserve">
|
||||
<source>Desktop address</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Desktop app version %@ is not compatible with this app." xml:space="preserve">
|
||||
<source>Desktop app version %@ is not compatible with this app.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Desktop devices" xml:space="preserve">
|
||||
<source>Desktop devices</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Develop" xml:space="preserve">
|
||||
<source>Develop</source>
|
||||
<target>Розробник</target>
|
||||
@@ -1706,18 +1850,16 @@
|
||||
<target>Від'єднати</target>
|
||||
<note>server test step</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Disconnect desktop?" xml:space="preserve">
|
||||
<source>Disconnect desktop?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Discover and join groups" xml:space="preserve">
|
||||
<source>Discover and join groups</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Display name" xml:space="preserve">
|
||||
<source>Display name</source>
|
||||
<target>Відображуване ім'я</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Display name:" xml:space="preserve">
|
||||
<source>Display name:</source>
|
||||
<target>Відображуване ім'я:</target>
|
||||
<trans-unit id="Discover via local network" xml:space="preserve">
|
||||
<source>Discover via local network</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Do NOT use SimpleX for emergency calls." xml:space="preserve">
|
||||
@@ -1888,6 +2030,14 @@
|
||||
<target>Зашифроване повідомлення: несподівана помилка</target>
|
||||
<note>notification</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Encryption re-negotiation error" xml:space="preserve">
|
||||
<source>Encryption re-negotiation error</source>
|
||||
<note>message decrypt error item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Encryption re-negotiation failed." xml:space="preserve">
|
||||
<source>Encryption re-negotiation failed.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter Passcode" xml:space="preserve">
|
||||
<source>Enter Passcode</source>
|
||||
<target>Введіть пароль</target>
|
||||
@@ -1898,6 +2048,10 @@
|
||||
<target>Введіть правильну парольну фразу.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter group name…" xml:space="preserve">
|
||||
<source>Enter group name…</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter passphrase…" xml:space="preserve">
|
||||
<source>Enter passphrase…</source>
|
||||
<target>Введіть пароль…</target>
|
||||
@@ -1913,6 +2067,10 @@
|
||||
<target>Увійдіть на сервер вручну</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter this device name…" xml:space="preserve">
|
||||
<source>Enter this device name…</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter welcome message…" xml:space="preserve">
|
||||
<source>Enter welcome message…</source>
|
||||
<target>Введіть вітальне повідомлення…</target>
|
||||
@@ -1923,6 +2081,10 @@
|
||||
<target>Введіть вітальне повідомлення... (необов'язково)</target>
|
||||
<note>placeholder</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter your name…" xml:space="preserve">
|
||||
<source>Enter your name…</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error" xml:space="preserve">
|
||||
<source>Error</source>
|
||||
<target>Помилка</target>
|
||||
@@ -2195,6 +2357,10 @@
|
||||
<target>Вихід без збереження</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Expand" xml:space="preserve">
|
||||
<source>Expand</source>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Export database" xml:space="preserve">
|
||||
<source>Export database</source>
|
||||
<target>Експорт бази даних</target>
|
||||
@@ -2225,6 +2391,10 @@
|
||||
<target>Швидко і без очікування, поки відправник буде онлайн!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Faster joining and more reliable messages." xml:space="preserve">
|
||||
<source>Faster joining and more reliable messages.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Favorite" xml:space="preserve">
|
||||
<source>Favorite</source>
|
||||
<target>Улюблений</target>
|
||||
@@ -2320,6 +2490,10 @@
|
||||
<target>Для консолі</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Found desktop" xml:space="preserve">
|
||||
<source>Found desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="French interface" xml:space="preserve">
|
||||
<source>French interface</source>
|
||||
<target>Французький інтерфейс</target>
|
||||
@@ -2340,6 +2514,10 @@
|
||||
<target>Повне ім'я:</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Fully decentralized – visible only to members." xml:space="preserve">
|
||||
<source>Fully decentralized – visible only to members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Fully re-implemented - work in background!" xml:space="preserve">
|
||||
<source>Fully re-implemented - work in background!</source>
|
||||
<target>Повністю перероблено - робота у фоновому режимі!</target>
|
||||
@@ -2360,6 +2538,14 @@
|
||||
<target>Група</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group already exists" xml:space="preserve">
|
||||
<source>Group already exists</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group already exists!" xml:space="preserve">
|
||||
<source>Group already exists!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Group display name" xml:space="preserve">
|
||||
<source>Group display name</source>
|
||||
<target>Назва групи для відображення</target>
|
||||
@@ -2630,6 +2816,10 @@
|
||||
<target>Інкогніто</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incognito groups" xml:space="preserve">
|
||||
<source>Incognito groups</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incognito mode" xml:space="preserve">
|
||||
<source>Incognito mode</source>
|
||||
<target>Режим інкогніто</target>
|
||||
@@ -2660,6 +2850,10 @@
|
||||
<target>Несумісна версія бази даних</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incompatible version" xml:space="preserve">
|
||||
<source>Incompatible version</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Incorrect passcode" xml:space="preserve">
|
||||
<source>Incorrect passcode</source>
|
||||
<target>Неправильний пароль</target>
|
||||
@@ -2707,6 +2901,10 @@
|
||||
<target>Неправильне посилання для підключення</target>
|
||||
<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 server address!" xml:space="preserve">
|
||||
<source>Invalid server address!</source>
|
||||
<target>Неправильна адреса сервера!</target>
|
||||
@@ -2798,16 +2996,33 @@
|
||||
<target>Приєднуйтесь до групи</target>
|
||||
<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>
|
||||
</trans-unit>
|
||||
<trans-unit id="Join incognito" xml:space="preserve">
|
||||
<source>Join incognito</source>
|
||||
<target>Приєднуйтесь інкогніто</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Join with current profile" xml:space="preserve">
|
||||
<source>Join with current profile</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Join your group? This is your link for group %@!" xml:space="preserve">
|
||||
<source>Join your group?
|
||||
This is your link for group %@!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Joining group" xml:space="preserve">
|
||||
<source>Joining group</source>
|
||||
<target>Приєднання до групи</target>
|
||||
<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 your connections" xml:space="preserve">
|
||||
<source>Keep your connections</source>
|
||||
<target>Зберігайте свої зв'язки</target>
|
||||
@@ -2868,6 +3083,18 @@
|
||||
<target>Обмеження</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Link mobile and desktop apps! 🔗" xml:space="preserve">
|
||||
<source>Link mobile and desktop apps! 🔗</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Linked desktop options" xml:space="preserve">
|
||||
<source>Linked desktop options</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Linked desktops" xml:space="preserve">
|
||||
<source>Linked desktops</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Live message!" xml:space="preserve">
|
||||
<source>Live message!</source>
|
||||
<target>Живе повідомлення!</target>
|
||||
@@ -3018,6 +3245,10 @@
|
||||
<target>Повідомлення та файли</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Messages from %@ will be shown!" xml:space="preserve">
|
||||
<source>Messages from %@ will be shown!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrating database archive…" xml:space="preserve">
|
||||
<source>Migrating database archive…</source>
|
||||
<target>Перенесення архіву бази даних…</target>
|
||||
@@ -3212,6 +3443,10 @@
|
||||
<target>Немає отриманих або відправлених файлів</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Not compatible!" xml:space="preserve">
|
||||
<source>Not compatible!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Notifications" xml:space="preserve">
|
||||
<source>Notifications</source>
|
||||
<target>Сповіщення</target>
|
||||
@@ -3365,6 +3600,10 @@
|
||||
<target>Відкрийте консоль чату</target>
|
||||
<note>authentication reason</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open group" xml:space="preserve">
|
||||
<source>Open group</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open user profiles" xml:space="preserve">
|
||||
<source>Open user profiles</source>
|
||||
<target>Відкрити профілі користувачів</target>
|
||||
@@ -3380,11 +3619,6 @@
|
||||
<target>Відкриття бази даних…</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red." xml:space="preserve">
|
||||
<source>Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red.</source>
|
||||
<target>Відкриття посилання в браузері може знизити конфіденційність і безпеку з'єднання. Ненадійні посилання SimpleX будуть червоного кольору.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="PING count" xml:space="preserve">
|
||||
<source>PING count</source>
|
||||
<target>Кількість PING</target>
|
||||
@@ -3430,6 +3664,10 @@
|
||||
<target>Вставити</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste desktop address" xml:space="preserve">
|
||||
<source>Paste desktop address</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste image" xml:space="preserve">
|
||||
<source>Paste image</source>
|
||||
<target>Вставити зображення</target>
|
||||
@@ -3575,6 +3813,14 @@
|
||||
<target>Зображення профілю</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Profile name" xml:space="preserve">
|
||||
<source>Profile name</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Profile name:" xml:space="preserve">
|
||||
<source>Profile name:</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Profile password" xml:space="preserve">
|
||||
<source>Profile password</source>
|
||||
<target>Пароль до профілю</target>
|
||||
@@ -3820,6 +4066,14 @@
|
||||
<target>Переузгодьте шифрування?</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Repeat connection request?" xml:space="preserve">
|
||||
<source>Repeat connection request?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Repeat join request?" xml:space="preserve">
|
||||
<source>Repeat join request?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reply" xml:space="preserve">
|
||||
<source>Reply</source>
|
||||
<target>Відповісти</target>
|
||||
@@ -4005,6 +4259,10 @@
|
||||
<target>Відскануйте QR-код</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Scan QR code from desktop" xml:space="preserve">
|
||||
<source>Scan QR code from desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Scan code" xml:space="preserve">
|
||||
<source>Scan code</source>
|
||||
<target>Сканувати код</target>
|
||||
@@ -4224,6 +4482,10 @@
|
||||
<target>Сервери</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Session code" xml:space="preserve">
|
||||
<source>Session code</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Set 1 day" xml:space="preserve">
|
||||
<source>Set 1 day</source>
|
||||
<target>Встановити 1 день</target>
|
||||
@@ -4533,6 +4795,10 @@
|
||||
<target>Натисніть кнопку </target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to Connect" xml:space="preserve">
|
||||
<source>Tap to Connect</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to activate profile." xml:space="preserve">
|
||||
<source>Tap to activate profile.</source>
|
||||
<target>Натисніть, щоб активувати профіль.</target>
|
||||
@@ -4630,11 +4896,6 @@ 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 group is fully decentralized – it is visible only to the members." xml:space="preserve">
|
||||
<source>The group is fully decentralized – it is visible only to the members.</source>
|
||||
<target>Група повністю децентралізована - її бачать лише учасники.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The hash of the previous message is different." xml:space="preserve">
|
||||
<source>The hash of the previous message is different.</source>
|
||||
<target>Хеш попереднього повідомлення відрізняється.</target>
|
||||
@@ -4720,6 +4981,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="This device name" xml:space="preserve">
|
||||
<source>This device 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>
|
||||
@@ -4730,6 +4995,14 @@ 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="This is your own SimpleX address!" xml:space="preserve">
|
||||
<source>This is your own SimpleX address!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This is your own one-time link!" xml:space="preserve">
|
||||
<source>This is your own one-time link!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This setting applies to messages in your current chat profile **%@**." xml:space="preserve">
|
||||
<source>This setting applies to messages in your current chat profile **%@**.</source>
|
||||
<target>Це налаштування застосовується до повідомлень у вашому поточному профілі чату **%@**.</target>
|
||||
@@ -4745,6 +5018,10 @@ It can happen because of some bug or when the connection is compromised.</source
|
||||
<target>Щоб підключитися, ваш контакт може відсканувати QR-код або скористатися посиланням у додатку.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="To hide unwanted messages." xml:space="preserve">
|
||||
<source>To hide unwanted messages.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="To make a new connection" xml:space="preserve">
|
||||
<source>To make a new connection</source>
|
||||
<target>Щоб створити нове з'єднання</target>
|
||||
@@ -4826,6 +5103,18 @@ 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="Unblock" xml:space="preserve">
|
||||
<source>Unblock</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?" xml:space="preserve">
|
||||
<source>Unblock member?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unexpected error: %@" xml:space="preserve">
|
||||
<source>Unexpected error: %@</source>
|
||||
<target>Неочікувана помилка: %@</target>
|
||||
@@ -4888,6 +5177,14 @@ To connect, please ask your contact to create another connection link and check
|
||||
Щоб підключитися, попросіть вашого контакта створити інше посилання і перевірте, чи маєте ви стабільне з'єднання з мережею.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unlink" xml:space="preserve">
|
||||
<source>Unlink</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unlink desktop?" xml:space="preserve">
|
||||
<source>Unlink desktop?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unlock" xml:space="preserve">
|
||||
<source>Unlock</source>
|
||||
<target>Розблокувати</target>
|
||||
@@ -4978,6 +5275,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 from desktop" xml:space="preserve">
|
||||
<source>Use from desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Use iOS call interface" xml:space="preserve">
|
||||
<source>Use iOS call interface</source>
|
||||
<target>Використовуйте інтерфейс виклику iOS</target>
|
||||
@@ -5008,11 +5309,23 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>Використання серверів SimpleX Chat.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify code with desktop" xml:space="preserve">
|
||||
<source>Verify code with desktop</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify connection" xml:space="preserve">
|
||||
<source>Verify connection</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify connection security" xml:space="preserve">
|
||||
<source>Verify connection security</source>
|
||||
<target>Перевірте безпеку з'єднання</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify connections" xml:space="preserve">
|
||||
<source>Verify connections</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Verify security code" xml:space="preserve">
|
||||
<source>Verify security code</source>
|
||||
<target>Підтвердіть код безпеки</target>
|
||||
@@ -5023,6 +5336,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="Via secure quantum resistant protocol." xml:space="preserve">
|
||||
<source>Via secure quantum resistant protocol.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Video call" xml:space="preserve">
|
||||
<source>Video call</source>
|
||||
<target>Відеодзвінок</target>
|
||||
@@ -5073,6 +5390,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="Waiting for desktop..." xml:space="preserve">
|
||||
<source>Waiting for desktop...</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Waiting for file" xml:space="preserve">
|
||||
<source>Waiting for file</source>
|
||||
<target>Очікування файлу</target>
|
||||
@@ -5173,6 +5494,35 @@ 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="You are already connecting to %@." xml:space="preserve">
|
||||
<source>You are already connecting to %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are already connecting via this one-time link!" xml:space="preserve">
|
||||
<source>You are already connecting via this one-time link!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are already in group %@." xml:space="preserve">
|
||||
<source>You are already in group %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are already joining the group %@." xml:space="preserve">
|
||||
<source>You are already joining the group %@.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are already joining the group via this link!" xml:space="preserve">
|
||||
<source>You are already joining the group via this link!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are already joining the group via this link." xml:space="preserve">
|
||||
<source>You are already joining the group via this link.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are already joining the group! Repeat join request?" xml:space="preserve">
|
||||
<source>You are already joining the group!
|
||||
Repeat join request?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You are connected to the server used to receive messages from this contact." xml:space="preserve">
|
||||
<source>You are connected to the server used to receive messages from this contact.</source>
|
||||
<target>Ви підключені до сервера, який використовується для отримання повідомлень від цього контакту.</target>
|
||||
@@ -5268,6 +5618,15 @@ 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="You have already requested connection via this address!" xml:space="preserve">
|
||||
<source>You have already requested connection via this address!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You have already requested connection! Repeat connection request?" xml:space="preserve">
|
||||
<source>You have already requested connection!
|
||||
Repeat connection request?</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You have no chats" xml:space="preserve">
|
||||
<source>You have no chats</source>
|
||||
<target>У вас немає чатів</target>
|
||||
@@ -5318,6 +5677,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="You will be connected when group link host's device is online, please wait or check later!" xml:space="preserve">
|
||||
<source>You will be connected when group link host's device is online, please wait or check later!</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You will be connected when your connection request is accepted, please wait or check later!" xml:space="preserve">
|
||||
<source>You will be connected when your connection request is accepted, please wait or check later!</source>
|
||||
<target>Ви будете підключені, коли ваш запит на підключення буде прийнято, будь ласка, зачекайте або перевірте пізніше!</target>
|
||||
@@ -5333,9 +5696,8 @@ To connect, please ask your contact to create another connection link and check
|
||||
<target>Вам потрібно буде пройти автентифікацію при запуску або відновленні програми після 30 секунд роботи у фоновому режимі.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You will join a group this link refers to and connect to its group members." xml:space="preserve">
|
||||
<source>You will join a group this link refers to and connect to its group members.</source>
|
||||
<target>Ви приєднаєтеся до групи, на яку посилається це посилання, і з'єднаєтеся з її учасниками.</target>
|
||||
<trans-unit id="You will connect to all group members." xml:space="preserve">
|
||||
<source>You will connect to all group members.</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You will still receive calls and notifications from muted profiles when they are active." xml:space="preserve">
|
||||
@@ -5403,11 +5765,6 @@ 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="Your chat profile will be sent to group members" xml:space="preserve">
|
||||
<source>Your chat profile will be sent to group members</source>
|
||||
<target>Ваш профіль у чаті буде надіслано учасникам групи</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your chat profiles" xml:space="preserve">
|
||||
<source>Your chat profiles</source>
|
||||
<target>Ваші профілі чату</target>
|
||||
@@ -5462,6 +5819,10 @@ You can change it in Settings.</source>
|
||||
<target>Ваша конфіденційність</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your profile" xml:space="preserve">
|
||||
<source>Your profile</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Your profile **%@** will be shared." xml:space="preserve">
|
||||
<source>Your profile **%@** will be shared.</source>
|
||||
<target>Ваш профіль **%@** буде опублікований.</target>
|
||||
@@ -5554,11 +5915,19 @@ SimpleX servers cannot see your profile.</source>
|
||||
<target>завжди</target>
|
||||
<note>pref value</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="and %lld other events" xml:space="preserve">
|
||||
<source>and %lld other events</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="audio call (not e2e encrypted)" xml:space="preserve">
|
||||
<source>audio call (not e2e encrypted)</source>
|
||||
<target>аудіовиклик (без шифрування e2e)</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="author" xml:space="preserve">
|
||||
<source>author</source>
|
||||
<note>member role</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="bad message ID" xml:space="preserve">
|
||||
<source>bad message ID</source>
|
||||
<target>невірний ідентифікатор повідомлення</target>
|
||||
@@ -5569,6 +5938,10 @@ SimpleX servers cannot see your profile.</source>
|
||||
<target>невірний хеш повідомлення</target>
|
||||
<note>integrity error chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="blocked" xml:space="preserve">
|
||||
<source>blocked</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="bold" xml:space="preserve">
|
||||
<source>bold</source>
|
||||
<target>жирний</target>
|
||||
@@ -5738,6 +6111,10 @@ SimpleX servers cannot see your profile.</source>
|
||||
<target>видалено</target>
|
||||
<note>deleted chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="deleted contact" xml:space="preserve">
|
||||
<source>deleted contact</source>
|
||||
<note>rcv direct event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="deleted group" xml:space="preserve">
|
||||
<source>deleted group</source>
|
||||
<target>видалено групу</target>
|
||||
@@ -6022,7 +6399,8 @@ SimpleX servers cannot see your profile.</source>
|
||||
<source>off</source>
|
||||
<target>вимкнено</target>
|
||||
<note>enabled status
|
||||
group pref value</note>
|
||||
group pref value
|
||||
time to disappear</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="offered %@" xml:space="preserve">
|
||||
<source>offered %@</source>
|
||||
@@ -6039,11 +6417,6 @@ SimpleX servers cannot see your profile.</source>
|
||||
<target>увімкнено</target>
|
||||
<note>group pref value</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="or chat with the developers" xml:space="preserve">
|
||||
<source>or chat with the developers</source>
|
||||
<target>або поспілкуйтеся з розробниками</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="owner" xml:space="preserve">
|
||||
<source>owner</source>
|
||||
<target>власник</target>
|
||||
@@ -6133,6 +6506,10 @@ SimpleX servers cannot see your profile.</source>
|
||||
<target>оновлений профіль групи</target>
|
||||
<note>rcv group event chat item</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="v%@" xml:space="preserve">
|
||||
<source>v%@</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="v%@ (%@)" xml:space="preserve">
|
||||
<source>v%@ (%@)</source>
|
||||
<target>v%@ (%@)</target>
|
||||
@@ -6270,6 +6647,10 @@ SimpleX servers cannot see your profile.</source>
|
||||
<target>SimpleX використовує Face ID для локальної автентифікації</target>
|
||||
<note>Privacy - Face ID Usage Description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="NSLocalNetworkUsageDescription" xml:space="preserve">
|
||||
<source>SimpleX uses local network access to allow using user chat profile via desktop app on the same network.</source>
|
||||
<note>Privacy - Local Network Usage Description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="NSMicrophoneUsageDescription" xml:space="preserve">
|
||||
<source>SimpleX needs microphone access for audio and video calls, and to record voice messages.</source>
|
||||
<target>SimpleX потребує доступу до мікрофона для аудіо та відео дзвінків, а також для запису голосових повідомлень.</target>
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
"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 */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,8 @@
|
||||
"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 */
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
5C36027327F47AD5009F19D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C36027227F47AD5009F19D9 /* AppDelegate.swift */; };
|
||||
5C3A88CE27DF50170060F1C2 /* DetermineWidth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3A88CD27DF50170060F1C2 /* DetermineWidth.swift */; };
|
||||
5C3A88D127DF57800060F1C2 /* FramedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3A88D027DF57800060F1C2 /* FramedItemView.swift */; };
|
||||
5C3CCFCC2AE6BD3100C3F0C3 /* ConnectDesktopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3CCFCB2AE6BD3100C3F0C3 /* ConnectDesktopView.swift */; };
|
||||
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 */; };
|
||||
@@ -117,11 +118,13 @@
|
||||
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 */; };
|
||||
5CD089312AE59CB300669208 /* libHSsimplex-chat-5.4.0.2-d5Ky77yoZRFE1pplaEhZO-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD0892C2AE59CB300669208 /* libHSsimplex-chat-5.4.0.2-d5Ky77yoZRFE1pplaEhZO-ghc8.10.7.a */; };
|
||||
5CD089322AE59CB300669208 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD0892D2AE59CB300669208 /* libffi.a */; };
|
||||
5CD089332AE59CB300669208 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD0892E2AE59CB300669208 /* libgmpxx.a */; };
|
||||
5CD089342AE59CB300669208 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD0892F2AE59CB300669208 /* libgmp.a */; };
|
||||
5CD089352AE59CB300669208 /* libHSsimplex-chat-5.4.0.2-d5Ky77yoZRFE1pplaEhZO.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD089302AE59CB300669208 /* libHSsimplex-chat-5.4.0.2-d5Ky77yoZRFE1pplaEhZO.a */; };
|
||||
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 */; };
|
||||
5CD67B962B11416700C510B1 /* libHSsimplex-chat-5.4.0.6-95eerlCBwIgI8jyla1GCr9.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD67B912B11416600C510B1 /* libHSsimplex-chat-5.4.0.6-95eerlCBwIgI8jyla1GCr9.a */; };
|
||||
5CD67B972B11416700C510B1 /* libHSsimplex-chat-5.4.0.6-95eerlCBwIgI8jyla1GCr9-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD67B922B11416600C510B1 /* libHSsimplex-chat-5.4.0.6-95eerlCBwIgI8jyla1GCr9-ghc9.6.3.a */; };
|
||||
5CD67B982B11416700C510B1 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD67B932B11416600C510B1 /* libffi.a */; };
|
||||
5CD67B992B11416700C510B1 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD67B942B11416600C510B1 /* libgmp.a */; };
|
||||
5CD67B9A2B11416700C510B1 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD67B952B11416700C510B1 /* libgmpxx.a */; };
|
||||
5CDCAD482818589900503DA2 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CDCAD472818589900503DA2 /* NotificationService.swift */; };
|
||||
5CE2BA702845308900EC33A6 /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; };
|
||||
5CE2BA712845308900EC33A6 /* SimpleXChat.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
@@ -162,6 +165,11 @@
|
||||
64466DC829FC2B3B00E3D48D /* CreateSimpleXAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64466DC729FC2B3B00E3D48D /* CreateSimpleXAddress.swift */; };
|
||||
64466DCC29FFE3E800E3D48D /* MailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64466DCB29FFE3E800E3D48D /* MailView.swift */; };
|
||||
6448BBB628FA9D56000D2AB9 /* GroupLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */; };
|
||||
6449333A2AF8E51000AC506E /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933352AF8E51000AC506E /* libgmpxx.a */; };
|
||||
6449333B2AF8E51000AC506E /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933362AF8E51000AC506E /* libgmp.a */; };
|
||||
6449333C2AF8E51000AC506E /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933372AF8E51000AC506E /* libffi.a */; };
|
||||
6449333D2AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933382AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8-ghc9.6.3.a */; };
|
||||
6449333E2AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933392AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8.a */; };
|
||||
644EFFDE292BCD9D00525D5B /* ComposeVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */; };
|
||||
644EFFE0292CFD7F00525D5B /* CIVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFDF292CFD7F00525D5B /* CIVoiceView.swift */; };
|
||||
644EFFE2292D089800525D5B /* FramedCIVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFE1292D089800525D5B /* FramedCIVoiceView.swift */; };
|
||||
@@ -282,6 +290,7 @@
|
||||
5C36027227F47AD5009F19D9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
5C3A88CD27DF50170060F1C2 /* DetermineWidth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetermineWidth.swift; sourceTree = "<group>"; };
|
||||
5C3A88D027DF57800060F1C2 /* FramedItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FramedItemView.swift; sourceTree = "<group>"; };
|
||||
5C3CCFCB2AE6BD3100C3F0C3 /* ConnectDesktopView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectDesktopView.swift; sourceTree = "<group>"; };
|
||||
5C3F1D552842B68D00EC8A82 /* IntegrityErrorItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegrityErrorItemView.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
@@ -397,11 +406,13 @@
|
||||
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>"; };
|
||||
5CD0892C2AE59CB300669208 /* libHSsimplex-chat-5.4.0.2-d5Ky77yoZRFE1pplaEhZO-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.2-d5Ky77yoZRFE1pplaEhZO-ghc8.10.7.a"; sourceTree = "<group>"; };
|
||||
5CD0892D2AE59CB300669208 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5CD0892E2AE59CB300669208 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5CD0892F2AE59CB300669208 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5CD089302AE59CB300669208 /* libHSsimplex-chat-5.4.0.2-d5Ky77yoZRFE1pplaEhZO.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.2-d5Ky77yoZRFE1pplaEhZO.a"; 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>"; };
|
||||
5CD67B912B11416600C510B1 /* libHSsimplex-chat-5.4.0.6-95eerlCBwIgI8jyla1GCr9.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.6-95eerlCBwIgI8jyla1GCr9.a"; sourceTree = "<group>"; };
|
||||
5CD67B922B11416600C510B1 /* libHSsimplex-chat-5.4.0.6-95eerlCBwIgI8jyla1GCr9-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.6-95eerlCBwIgI8jyla1GCr9-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
5CD67B932B11416600C510B1 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5CD67B942B11416600C510B1 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5CD67B952B11416700C510B1 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5CDCAD452818589900503DA2 /* SimpleX NSE.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "SimpleX NSE.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
5CDCAD472818589900503DA2 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
||||
5CDCAD492818589900503DA2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
@@ -442,6 +453,11 @@
|
||||
64466DC729FC2B3B00E3D48D /* CreateSimpleXAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSimpleXAddress.swift; sourceTree = "<group>"; };
|
||||
64466DCB29FFE3E800E3D48D /* MailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailView.swift; sourceTree = "<group>"; };
|
||||
6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupLinkView.swift; sourceTree = "<group>"; };
|
||||
644933352AF8E51000AC506E /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
644933362AF8E51000AC506E /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
644933372AF8E51000AC506E /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
644933382AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
644933392AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8.a"; sourceTree = "<group>"; };
|
||||
644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeVoiceView.swift; sourceTree = "<group>"; };
|
||||
644EFFDF292CFD7F00525D5B /* CIVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIVoiceView.swift; sourceTree = "<group>"; };
|
||||
644EFFE1292D089800525D5B /* FramedCIVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FramedCIVoiceView.swift; sourceTree = "<group>"; };
|
||||
@@ -505,13 +521,13 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5CD089352AE59CB300669208 /* libHSsimplex-chat-5.4.0.2-d5Ky77yoZRFE1pplaEhZO.a in Frameworks */,
|
||||
5CD67B972B11416700C510B1 /* libHSsimplex-chat-5.4.0.6-95eerlCBwIgI8jyla1GCr9-ghc9.6.3.a in Frameworks */,
|
||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||
5CD089332AE59CB300669208 /* libgmpxx.a in Frameworks */,
|
||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||
5CD089312AE59CB300669208 /* libHSsimplex-chat-5.4.0.2-d5Ky77yoZRFE1pplaEhZO-ghc8.10.7.a in Frameworks */,
|
||||
5CD089342AE59CB300669208 /* libgmp.a in Frameworks */,
|
||||
5CD089322AE59CB300669208 /* libffi.a in Frameworks */,
|
||||
5CD67B982B11416700C510B1 /* libffi.a in Frameworks */,
|
||||
5CD67B992B11416700C510B1 /* libgmp.a in Frameworks */,
|
||||
5CD67B962B11416700C510B1 /* libHSsimplex-chat-5.4.0.6-95eerlCBwIgI8jyla1GCr9.a in Frameworks */,
|
||||
5CD67B9A2B11416700C510B1 /* libgmpxx.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -544,6 +560,7 @@
|
||||
5CB924DD27A8622200ACCCDD /* NewChat */,
|
||||
5CFA59C22860B04D00863A68 /* Database */,
|
||||
5CB634AB29E46CDB0066AD6B /* LocalAuth */,
|
||||
5CA8D01B2AD9B076001FD661 /* RemoteAccess */,
|
||||
5CB924DF27A8678B00ACCCDD /* UserSettings */,
|
||||
5C2E261127A30FEA00F70299 /* TerminalView.swift */,
|
||||
);
|
||||
@@ -572,11 +589,11 @@
|
||||
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5CD0892D2AE59CB300669208 /* libffi.a */,
|
||||
5CD0892F2AE59CB300669208 /* libgmp.a */,
|
||||
5CD0892E2AE59CB300669208 /* libgmpxx.a */,
|
||||
5CD0892C2AE59CB300669208 /* libHSsimplex-chat-5.4.0.2-d5Ky77yoZRFE1pplaEhZO-ghc8.10.7.a */,
|
||||
5CD089302AE59CB300669208 /* libHSsimplex-chat-5.4.0.2-d5Ky77yoZRFE1pplaEhZO.a */,
|
||||
5CD67B932B11416600C510B1 /* libffi.a */,
|
||||
5CD67B942B11416600C510B1 /* libgmp.a */,
|
||||
5CD67B952B11416700C510B1 /* libgmpxx.a */,
|
||||
5CD67B922B11416600C510B1 /* libHSsimplex-chat-5.4.0.6-95eerlCBwIgI8jyla1GCr9-ghc9.6.3.a */,
|
||||
5CD67B912B11416600C510B1 /* libHSsimplex-chat-5.4.0.6-95eerlCBwIgI8jyla1GCr9.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
@@ -684,6 +701,14 @@
|
||||
path = "Tests iOS";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5CA8D01B2AD9B076001FD661 /* RemoteAccess */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5C3CCFCB2AE6BD3100C3F0C3 /* ConnectDesktopView.swift */,
|
||||
);
|
||||
path = RemoteAccess;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5CB0BA8C282711BC00B3292C /* Onboarding */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -797,6 +822,8 @@
|
||||
5CE2BA8A2845332200EC33A6 /* SimpleX.h */,
|
||||
5CE2BA78284530CC00EC33A6 /* SimpleXChat.docc */,
|
||||
5CE2BA96284537A800EC33A6 /* dummy.m */,
|
||||
5CD67B8D2B0E858A00C510B1 /* hs_init.h */,
|
||||
5CD67B8E2B0E858A00C510B1 /* hs_init.c */,
|
||||
);
|
||||
path = SimpleXChat;
|
||||
sourceTree = "<group>";
|
||||
@@ -881,6 +908,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5CE2BA77284530BF00EC33A6 /* SimpleXChat.h in Headers */,
|
||||
5CD67B8F2B0E858A00C510B1 /* hs_init.h in Headers */,
|
||||
5CE2BA952845354B00EC33A6 /* SimpleX.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -1170,6 +1198,7 @@
|
||||
6454036F2822A9750090DDFF /* ComposeFileView.swift in Sources */,
|
||||
5C5DB70E289ABDD200730FFF /* AppearanceSettings.swift in Sources */,
|
||||
5C5F2B6D27EBC3FE006A9D5F /* ImagePicker.swift in Sources */,
|
||||
5C3CCFCC2AE6BD3100C3F0C3 /* ConnectDesktopView.swift in Sources */,
|
||||
5C9C2DA92899DA6F00CC63B1 /* NetworkAndServers.swift in Sources */,
|
||||
5C6BA667289BD954009B8ECC /* DismissSheets.swift in Sources */,
|
||||
5C577F7D27C83AA10006112D /* MarkdownHelp.swift in Sources */,
|
||||
@@ -1250,6 +1279,7 @@
|
||||
5C00168128C4FE760094D739 /* KeyChain.swift in Sources */,
|
||||
5CE2BA97284537A800EC33A6 /* dummy.m in Sources */,
|
||||
5CE2BA922845340900EC33A6 /* FileUtils.swift in Sources */,
|
||||
5CD67B902B0E858A00C510B1 /* hs_init.c in Sources */,
|
||||
5CE2BA91284533A300EC33A6 /* Notifications.swift in Sources */,
|
||||
5CE2BA79284530CC00EC33A6 /* SimpleXChat.docc in Sources */,
|
||||
5CE2BA90284533A300EC33A6 /* JSON.swift in Sources */,
|
||||
@@ -1482,7 +1512,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 180;
|
||||
CURRENT_PROJECT_VERSION = 183;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1490,6 +1520,7 @@
|
||||
INFOPLIST_FILE = "SimpleX--iOS--Info.plist";
|
||||
INFOPLIST_KEY_NSCameraUsageDescription = "SimpleX needs camera access to scan QR codes to connect to other users and for video calls.";
|
||||
INFOPLIST_KEY_NSFaceIDUsageDescription = "SimpleX uses Face ID for local authentication";
|
||||
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "SimpleX uses local network access to allow using user chat profile via desktop app on the same network.";
|
||||
INFOPLIST_KEY_NSMicrophoneUsageDescription = "SimpleX needs microphone access for audio and video calls, and to record voice messages.";
|
||||
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "SimpleX needs access to Photo Library for saving captured and received media";
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
@@ -1524,7 +1555,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 180;
|
||||
CURRENT_PROJECT_VERSION = 183;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1532,6 +1563,7 @@
|
||||
INFOPLIST_FILE = "SimpleX--iOS--Info.plist";
|
||||
INFOPLIST_KEY_NSCameraUsageDescription = "SimpleX needs camera access to scan QR codes to connect to other users and for video calls.";
|
||||
INFOPLIST_KEY_NSFaceIDUsageDescription = "SimpleX uses Face ID for local authentication";
|
||||
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "SimpleX uses local network access to allow using user chat profile via desktop app on the same network.";
|
||||
INFOPLIST_KEY_NSMicrophoneUsageDescription = "SimpleX needs microphone access for audio and video calls, and to record voice messages.";
|
||||
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "SimpleX needs access to Photo Library for saving captured and received media";
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
@@ -1604,7 +1636,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 180;
|
||||
CURRENT_PROJECT_VERSION = 183;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -1636,7 +1668,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 180;
|
||||
CURRENT_PROJECT_VERSION = 183;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -1668,7 +1700,7 @@
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 180;
|
||||
CURRENT_PROJECT_VERSION = 183;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1714,7 +1746,7 @@
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 180;
|
||||
CURRENT_PROJECT_VERSION = 183;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
|
||||
@@ -50,7 +50,7 @@ public enum ChatCommand {
|
||||
case apiVerifyToken(token: DeviceToken, nonce: String, code: String)
|
||||
case apiDeleteToken(token: DeviceToken)
|
||||
case apiGetNtfMessage(nonce: String, encNtfInfo: String)
|
||||
case apiNewGroup(userId: Int64, groupProfile: GroupProfile)
|
||||
case apiNewGroup(userId: Int64, incognito: Bool, groupProfile: GroupProfile)
|
||||
case apiAddMember(groupId: Int64, contactId: Int64, memberRole: GroupMemberRole)
|
||||
case apiJoinGroup(groupId: Int64)
|
||||
case apiMemberRole(groupId: Int64, memberId: Int64, memberRole: GroupMemberRole)
|
||||
@@ -73,6 +73,7 @@ public enum ChatCommand {
|
||||
case apiGetNetworkConfig
|
||||
case reconnectAllServers
|
||||
case apiSetChatSettings(type: ChatType, id: Int64, chatSettings: ChatSettings)
|
||||
case apiSetMemberSettings(groupId: Int64, groupMemberId: Int64, memberSettings: GroupMemberSettings)
|
||||
case apiContactInfo(contactId: Int64)
|
||||
case apiGroupMemberInfo(groupId: Int64, groupMemberId: Int64)
|
||||
case apiSwitchContact(contactId: Int64)
|
||||
@@ -89,6 +90,7 @@ public enum ChatCommand {
|
||||
case apiSetConnectionIncognito(connId: Int64, incognito: Bool)
|
||||
case apiConnectPlan(userId: Int64, connReq: String)
|
||||
case apiConnect(userId: Int64, incognito: Bool, connReq: String)
|
||||
case apiConnectContactViaAddress(userId: Int64, incognito: Bool, contactId: Int64)
|
||||
case apiDeleteChat(type: ChatType, id: Int64, notify: Bool?)
|
||||
case apiClearChat(type: ChatType, id: Int64)
|
||||
case apiListContacts(userId: Int64)
|
||||
@@ -118,6 +120,16 @@ public enum ChatCommand {
|
||||
case receiveFile(fileId: Int64, encrypted: Bool?, inline: Bool?)
|
||||
case setFileToReceive(fileId: Int64, encrypted: Bool?)
|
||||
case cancelFile(fileId: Int64)
|
||||
// remote desktop commands
|
||||
case setLocalDeviceName(displayName: String)
|
||||
case connectRemoteCtrl(xrcpInvitation: String)
|
||||
case findKnownRemoteCtrl
|
||||
case confirmRemoteCtrl(remoteCtrlId: Int64)
|
||||
case verifyRemoteCtrlSession(sessionCode: String)
|
||||
case listRemoteCtrls
|
||||
case stopRemoteCtrl
|
||||
case deleteRemoteCtrl(remoteCtrlId: Int64)
|
||||
// misc
|
||||
case showVersion
|
||||
case string(String)
|
||||
|
||||
@@ -175,7 +187,7 @@ public enum ChatCommand {
|
||||
case let .apiVerifyToken(token, nonce, code): return "/_ntf verify \(token.cmdString) \(nonce) \(code)"
|
||||
case let .apiDeleteToken(token): return "/_ntf delete \(token.cmdString)"
|
||||
case let .apiGetNtfMessage(nonce, encNtfInfo): return "/_ntf message \(nonce) \(encNtfInfo)"
|
||||
case let .apiNewGroup(userId, groupProfile): return "/_group \(userId) \(encodeJSON(groupProfile))"
|
||||
case let .apiNewGroup(userId, incognito, groupProfile): return "/_group \(userId) incognito=\(onOff(incognito)) \(encodeJSON(groupProfile))"
|
||||
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)"
|
||||
@@ -198,6 +210,7 @@ public enum ChatCommand {
|
||||
case .apiGetNetworkConfig: return "/network"
|
||||
case .reconnectAllServers: return "/reconnect"
|
||||
case let .apiSetChatSettings(type, id, chatSettings): return "/_settings \(ref(type, id)) \(encodeJSON(chatSettings))"
|
||||
case let .apiSetMemberSettings(groupId, groupMemberId, memberSettings): return "/_member settings #\(groupId) \(groupMemberId) \(encodeJSON(memberSettings))"
|
||||
case let .apiContactInfo(contactId): return "/_info @\(contactId)"
|
||||
case let .apiGroupMemberInfo(groupId, groupMemberId): return "/_info #\(groupId) \(groupMemberId)"
|
||||
case let .apiSwitchContact(contactId): return "/_switch @\(contactId)"
|
||||
@@ -224,6 +237,7 @@ public enum ChatCommand {
|
||||
case let .apiSetConnectionIncognito(connId, incognito): return "/_set incognito :\(connId) \(onOff(incognito))"
|
||||
case let .apiConnectPlan(userId, connReq): return "/_connect plan \(userId) \(connReq)"
|
||||
case let .apiConnect(userId, incognito, connReq): return "/_connect \(userId) incognito=\(onOff(incognito)) \(connReq)"
|
||||
case let .apiConnectContactViaAddress(userId, incognito, contactId): return "/_connect contact \(userId) incognito=\(onOff(incognito)) \(contactId)"
|
||||
case let .apiDeleteChat(type, id, notify): if let notify = notify {
|
||||
return "/_delete \(ref(type, id)) notify=\(onOff(notify))"
|
||||
} else {
|
||||
@@ -256,6 +270,14 @@ public enum ChatCommand {
|
||||
case let .receiveFile(fileId, encrypt, inline): return "/freceive \(fileId)\(onOffParam("encrypt", encrypt))\(onOffParam("inline", inline))"
|
||||
case let .setFileToReceive(fileId, encrypt): return "/_set_file_to_receive \(fileId)\(onOffParam("encrypt", encrypt))"
|
||||
case let .cancelFile(fileId): return "/fcancel \(fileId)"
|
||||
case let .setLocalDeviceName(displayName): return "/set device name \(displayName)"
|
||||
case let .connectRemoteCtrl(xrcpInv): return "/connect remote ctrl \(xrcpInv)"
|
||||
case .findKnownRemoteCtrl: return "/find remote ctrl"
|
||||
case let .confirmRemoteCtrl(rcId): return "/confirm remote ctrl \(rcId)"
|
||||
case let .verifyRemoteCtrlSession(sessCode): return "/verify remote ctrl \(sessCode)"
|
||||
case .listRemoteCtrls: return "/list remote ctrls"
|
||||
case .stopRemoteCtrl: return "/stop remote ctrl"
|
||||
case let .deleteRemoteCtrl(rcId): return "/delete remote ctrl \(rcId)"
|
||||
case .showVersion: return "/version"
|
||||
case let .string(str): return str
|
||||
}
|
||||
@@ -295,6 +317,7 @@ public enum ChatCommand {
|
||||
case .apiSendMessage: return "apiSendMessage"
|
||||
case .apiUpdateChatItem: return "apiUpdateChatItem"
|
||||
case .apiDeleteChatItem: return "apiDeleteChatItem"
|
||||
case .apiConnectContactViaAddress: return "apiConnectContactViaAddress"
|
||||
case .apiDeleteMemberChatItem: return "apiDeleteMemberChatItem"
|
||||
case .apiChatItemReaction: return "apiChatItemReaction"
|
||||
case .apiGetNtfToken: return "apiGetNtfToken"
|
||||
@@ -325,6 +348,7 @@ public enum ChatCommand {
|
||||
case .apiGetNetworkConfig: return "apiGetNetworkConfig"
|
||||
case .reconnectAllServers: return "reconnectAllServers"
|
||||
case .apiSetChatSettings: return "apiSetChatSettings"
|
||||
case .apiSetMemberSettings: return "apiSetMemberSettings"
|
||||
case .apiContactInfo: return "apiContactInfo"
|
||||
case .apiGroupMemberInfo: return "apiGroupMemberInfo"
|
||||
case .apiSwitchContact: return "apiSwitchContact"
|
||||
@@ -369,6 +393,14 @@ public enum ChatCommand {
|
||||
case .receiveFile: return "receiveFile"
|
||||
case .setFileToReceive: return "setFileToReceive"
|
||||
case .cancelFile: return "cancelFile"
|
||||
case .setLocalDeviceName: return "setLocalDeviceName"
|
||||
case .connectRemoteCtrl: return "connectRemoteCtrl"
|
||||
case .findKnownRemoteCtrl: return "findKnownRemoteCtrl"
|
||||
case .confirmRemoteCtrl: return "confirmRemoteCtrl"
|
||||
case .verifyRemoteCtrlSession: return "verifyRemoteCtrlSession"
|
||||
case .listRemoteCtrls: return "listRemoteCtrls"
|
||||
case .stopRemoteCtrl: return "stopRemoteCtrl"
|
||||
case .deleteRemoteCtrl: return "deleteRemoteCtrl"
|
||||
case .showVersion: return "showVersion"
|
||||
case .string: return "console command"
|
||||
}
|
||||
@@ -453,7 +485,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case serverTestResult(user: UserRef, testServer: String, testFailure: ProtocolTestFailure?)
|
||||
case chatItemTTL(user: UserRef, chatItemTTL: Int64?)
|
||||
case networkConfig(networkConfig: NetCfg)
|
||||
case contactInfo(user: UserRef, contact: Contact, connectionStats: ConnectionStats, customUserProfile: Profile?)
|
||||
case contactInfo(user: UserRef, contact: Contact, connectionStats_: ConnectionStats?, customUserProfile: Profile?)
|
||||
case groupMemberInfo(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats_: ConnectionStats?)
|
||||
case contactSwitchStarted(user: UserRef, contact: Contact, connectionStats: ConnectionStats)
|
||||
case groupMemberSwitchStarted(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats)
|
||||
@@ -475,6 +507,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case connectionPlan(user: UserRef, connectionPlan: ConnectionPlan)
|
||||
case sentConfirmation(user: UserRef)
|
||||
case sentInvitation(user: UserRef)
|
||||
case sentInvitationToContact(user: UserRef, contact: Contact, customUserProfile: Profile?)
|
||||
case contactAlreadyExists(user: UserRef, contact: Contact)
|
||||
case contactRequestAlreadyAccepted(user: UserRef, contact: Contact)
|
||||
case contactDeleted(user: UserRef, contact: Contact)
|
||||
@@ -496,6 +529,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case acceptingContactRequest(user: UserRef, contact: Contact)
|
||||
case contactRequestRejected(user: UserRef)
|
||||
case contactUpdated(user: UserRef, toContact: Contact)
|
||||
case groupMemberUpdated(user: UserRef, groupInfo: GroupInfo, fromMember: GroupMember, toMember: GroupMember)
|
||||
// TODO remove events below
|
||||
case contactsSubscribed(server: String, contactRefs: [ContactRef])
|
||||
case contactsDisconnected(server: String, contactRefs: [ContactRef])
|
||||
@@ -518,6 +552,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case groupCreated(user: UserRef, groupInfo: GroupInfo)
|
||||
case sentGroupInvitation(user: UserRef, groupInfo: GroupInfo, contact: Contact, member: GroupMember)
|
||||
case userAcceptedGroupSent(user: UserRef, groupInfo: GroupInfo, hostContact: Contact?)
|
||||
case groupLinkConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember)
|
||||
case userDeletedMember(user: UserRef, groupInfo: GroupInfo, member: GroupMember)
|
||||
case leftMemberUser(user: UserRef, groupInfo: GroupInfo)
|
||||
case groupMembers(user: UserRef, group: Group)
|
||||
@@ -572,6 +607,14 @@ public enum ChatResponse: Decodable, Error {
|
||||
case ntfMessages(user_: User?, connEntity: ConnectionEntity?, msgTs: Date?, ntfMessages: [NtfMsgInfo])
|
||||
case newContactConnection(user: UserRef, connection: PendingContactConnection)
|
||||
case contactConnectionDeleted(user: UserRef, connection: PendingContactConnection)
|
||||
// remote desktop responses/events
|
||||
case remoteCtrlList(remoteCtrls: [RemoteCtrlInfo])
|
||||
case remoteCtrlFound(remoteCtrl: RemoteCtrlInfo, ctrlAppInfo_: CtrlAppInfo?, appVersion: String, compatible: Bool)
|
||||
case remoteCtrlConnecting(remoteCtrl_: RemoteCtrlInfo?, ctrlAppInfo: CtrlAppInfo, appVersion: String)
|
||||
case remoteCtrlSessionCode(remoteCtrl_: RemoteCtrlInfo?, sessionCode: String)
|
||||
case remoteCtrlConnected(remoteCtrl: RemoteCtrlInfo)
|
||||
case remoteCtrlStopped
|
||||
// misc
|
||||
case versionInfo(versionInfo: CoreVersionInfo, chatMigrations: [UpMigration], agentMigrations: [UpMigration])
|
||||
case cmdOk(user: UserRef?)
|
||||
case chatCmdError(user_: UserRef?, chatError: ChatError)
|
||||
@@ -617,6 +660,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case .connectionPlan: return "connectionPlan"
|
||||
case .sentConfirmation: return "sentConfirmation"
|
||||
case .sentInvitation: return "sentInvitation"
|
||||
case .sentInvitationToContact: return "sentInvitationToContact"
|
||||
case .contactAlreadyExists: return "contactAlreadyExists"
|
||||
case .contactRequestAlreadyAccepted: return "contactRequestAlreadyAccepted"
|
||||
case .contactDeleted: return "contactDeleted"
|
||||
@@ -638,6 +682,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case .acceptingContactRequest: return "acceptingContactRequest"
|
||||
case .contactRequestRejected: return "contactRequestRejected"
|
||||
case .contactUpdated: return "contactUpdated"
|
||||
case .groupMemberUpdated: return "groupMemberUpdated"
|
||||
case .contactsSubscribed: return "contactsSubscribed"
|
||||
case .contactsDisconnected: return "contactsDisconnected"
|
||||
case .contactSubSummary: return "contactSubSummary"
|
||||
@@ -657,6 +702,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case .groupCreated: return "groupCreated"
|
||||
case .sentGroupInvitation: return "sentGroupInvitation"
|
||||
case .userAcceptedGroupSent: return "userAcceptedGroupSent"
|
||||
case .groupLinkConnecting: return "groupLinkConnecting"
|
||||
case .userDeletedMember: return "userDeletedMember"
|
||||
case .leftMemberUser: return "leftMemberUser"
|
||||
case .groupMembers: return "groupMembers"
|
||||
@@ -708,6 +754,12 @@ public enum ChatResponse: Decodable, Error {
|
||||
case .ntfMessages: return "ntfMessages"
|
||||
case .newContactConnection: return "newContactConnection"
|
||||
case .contactConnectionDeleted: return "contactConnectionDeleted"
|
||||
case .remoteCtrlList: return "remoteCtrlList"
|
||||
case .remoteCtrlFound: return "remoteCtrlFound"
|
||||
case .remoteCtrlConnecting: return "remoteCtrlConnecting"
|
||||
case .remoteCtrlSessionCode: return "remoteCtrlSessionCode"
|
||||
case .remoteCtrlConnected: return "remoteCtrlConnected"
|
||||
case .remoteCtrlStopped: return "remoteCtrlStopped"
|
||||
case .versionInfo: return "versionInfo"
|
||||
case .cmdOk: return "cmdOk"
|
||||
case .chatCmdError: return "chatCmdError"
|
||||
@@ -734,7 +786,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case let .serverTestResult(u, server, testFailure): return withUser(u, "server: \(server)\nresult: \(String(describing: testFailure))")
|
||||
case let .chatItemTTL(u, chatItemTTL): return withUser(u, String(describing: chatItemTTL))
|
||||
case let .networkConfig(networkConfig): return String(describing: networkConfig)
|
||||
case let .contactInfo(u, contact, connectionStats, customUserProfile): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))\ncustomUserProfile: \(String(describing: customUserProfile))")
|
||||
case let .contactInfo(u, contact, connectionStats_, customUserProfile): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats_: \(String(describing: connectionStats_))\ncustomUserProfile: \(String(describing: customUserProfile))")
|
||||
case let .groupMemberInfo(u, groupInfo, member, connectionStats_): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats_: \(String(describing: connectionStats_))")
|
||||
case let .contactSwitchStarted(u, contact, connectionStats): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))")
|
||||
case let .groupMemberSwitchStarted(u, groupInfo, member, connectionStats): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats: \(String(describing: connectionStats))")
|
||||
@@ -756,6 +808,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case let .connectionPlan(u, connectionPlan): return withUser(u, String(describing: connectionPlan))
|
||||
case .sentConfirmation: return noDetails
|
||||
case .sentInvitation: return noDetails
|
||||
case let .sentInvitationToContact(u, contact, _): return withUser(u, String(describing: contact))
|
||||
case let .contactAlreadyExists(u, contact): return withUser(u, String(describing: contact))
|
||||
case let .contactRequestAlreadyAccepted(u, contact): return withUser(u, String(describing: contact))
|
||||
case let .contactDeleted(u, contact): return withUser(u, String(describing: contact))
|
||||
@@ -777,6 +830,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case let .acceptingContactRequest(u, contact): return withUser(u, String(describing: contact))
|
||||
case .contactRequestRejected: return noDetails
|
||||
case let .contactUpdated(u, toContact): return withUser(u, String(describing: toContact))
|
||||
case let .groupMemberUpdated(u, groupInfo, fromMember, toMember): return withUser(u, "groupInfo: \(groupInfo)\nfromMember: \(fromMember)\ntoMember: \(toMember)")
|
||||
case let .contactsSubscribed(server, contactRefs): return "server: \(server)\ncontacts:\n\(String(describing: contactRefs))"
|
||||
case let .contactsDisconnected(server, contactRefs): return "server: \(server)\ncontacts:\n\(String(describing: contactRefs))"
|
||||
case let .contactSubSummary(u, contactSubscriptions): return withUser(u, String(describing: contactSubscriptions))
|
||||
@@ -796,6 +850,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case let .groupCreated(u, groupInfo): return withUser(u, String(describing: groupInfo))
|
||||
case let .sentGroupInvitation(u, groupInfo, contact, member): return withUser(u, "groupInfo: \(groupInfo)\ncontact: \(contact)\nmember: \(member)")
|
||||
case let .userAcceptedGroupSent(u, groupInfo, hostContact): return withUser(u, "groupInfo: \(groupInfo)\nhostContact: \(String(describing: hostContact))")
|
||||
case let .groupLinkConnecting(u, groupInfo, hostMember): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(String(describing: hostMember))")
|
||||
case let .userDeletedMember(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)")
|
||||
case let .leftMemberUser(u, groupInfo): return withUser(u, String(describing: groupInfo))
|
||||
case let .groupMembers(u, group): return withUser(u, String(describing: group))
|
||||
@@ -847,6 +902,12 @@ public enum ChatResponse: Decodable, Error {
|
||||
case let .ntfMessages(u, connEntity, msgTs, ntfMessages): return withUser(u, "connEntity: \(String(describing: connEntity))\nmsgTs: \(String(describing: msgTs))\nntfMessages: \(String(describing: ntfMessages))")
|
||||
case let .newContactConnection(u, connection): return withUser(u, String(describing: connection))
|
||||
case let .contactConnectionDeleted(u, connection): return withUser(u, String(describing: connection))
|
||||
case let .remoteCtrlList(remoteCtrls): return String(describing: remoteCtrls)
|
||||
case let .remoteCtrlFound(remoteCtrl, ctrlAppInfo_, appVersion, compatible): return "remoteCtrl:\n\(String(describing: remoteCtrl))\nctrlAppInfo_:\n\(String(describing: ctrlAppInfo_))\nappVersion: \(appVersion)\ncompatible: \(compatible)"
|
||||
case let .remoteCtrlConnecting(remoteCtrl_, ctrlAppInfo, appVersion): return "remoteCtrl_:\n\(String(describing: remoteCtrl_))\nctrlAppInfo:\n\(String(describing: ctrlAppInfo))\nappVersion: \(appVersion)"
|
||||
case let .remoteCtrlSessionCode(remoteCtrl_, sessionCode): return "remoteCtrl_:\n\(String(describing: remoteCtrl_))\nsessionCode: \(sessionCode)"
|
||||
case let .remoteCtrlConnected(remoteCtrl): return String(describing: remoteCtrl)
|
||||
case .remoteCtrlStopped: return noDetails
|
||||
case let .versionInfo(versionInfo, chatMigrations, agentMigrations): return "\(String(describing: versionInfo))\n\nchat migrations: \(chatMigrations.map(\.upName))\n\nagent migrations: \(agentMigrations.map(\.upName))"
|
||||
case .cmdOk: return noDetails
|
||||
case let .chatCmdError(u, chatError): return withUser(u, String(describing: chatError))
|
||||
@@ -893,6 +954,7 @@ public enum ContactAddressPlan: Decodable {
|
||||
case connectingConfirmReconnect
|
||||
case connectingProhibit(contact: Contact)
|
||||
case known(contact: Contact)
|
||||
case contactViaAddress(contact: Contact)
|
||||
}
|
||||
|
||||
public enum GroupLinkPlan: Decodable {
|
||||
@@ -1472,6 +1534,34 @@ public enum NotificationPreviewMode: String, SelectableItem {
|
||||
public static var values: [NotificationPreviewMode] = [.message, .contact, .hidden]
|
||||
}
|
||||
|
||||
public struct RemoteCtrlInfo: Decodable {
|
||||
public var remoteCtrlId: Int64
|
||||
public var ctrlDeviceName: String
|
||||
public var sessionState: RemoteCtrlSessionState?
|
||||
|
||||
public var deviceViewName: String {
|
||||
ctrlDeviceName == "" ? "\(remoteCtrlId)" : ctrlDeviceName
|
||||
}
|
||||
}
|
||||
|
||||
public enum RemoteCtrlSessionState: Decodable {
|
||||
case starting
|
||||
case searching
|
||||
case connecting
|
||||
case pendingConfirmation(sessionCode: String)
|
||||
case connected(sessionCode: String)
|
||||
}
|
||||
|
||||
public struct CtrlAppInfo: Decodable {
|
||||
public var appVersionRange: AppVersionRange
|
||||
public var deviceName: String
|
||||
}
|
||||
|
||||
public struct AppVersionRange: Decodable {
|
||||
public var minVersion: String
|
||||
public var maxVersion: String
|
||||
}
|
||||
|
||||
public struct CoreVersionInfo: Decodable {
|
||||
public var version: String
|
||||
public var simplexmqVersion: String
|
||||
@@ -1499,6 +1589,7 @@ public enum ChatError: Decodable {
|
||||
case errorAgent(agentError: AgentErrorType)
|
||||
case errorStore(storeError: StoreError)
|
||||
case errorDatabase(databaseError: DatabaseError)
|
||||
case errorRemoteCtrl(remoteCtrlError: RemoteCtrlError)
|
||||
case invalidJSON(json: String)
|
||||
}
|
||||
|
||||
@@ -1658,6 +1749,7 @@ public enum AgentErrorType: Decodable {
|
||||
case SMP(smpErr: ProtocolErrorType)
|
||||
case NTF(ntfErr: ProtocolErrorType)
|
||||
case XFTP(xftpErr: XFTPErrorType)
|
||||
case RCP(rcpErr: RCErrorType)
|
||||
case BROKER(brokerAddress: String, brokerErr: BrokerErrorType)
|
||||
case AGENT(agentErr: SMPAgentError)
|
||||
case INTERNAL(internalErr: String)
|
||||
@@ -1715,6 +1807,22 @@ public enum XFTPErrorType: Decodable {
|
||||
case INTERNAL
|
||||
}
|
||||
|
||||
public enum RCErrorType: Decodable {
|
||||
case `internal`(internalErr: String)
|
||||
case identity
|
||||
case noLocalAddress
|
||||
case tlsStartFailed
|
||||
case exception(exception: String)
|
||||
case ctrlAuth
|
||||
case ctrlNotFound
|
||||
case ctrlError(ctrlErr: String)
|
||||
case version
|
||||
case encrypt
|
||||
case decrypt
|
||||
case blockSize
|
||||
case syntax(syntaxErr: String)
|
||||
}
|
||||
|
||||
public enum ProtocolCommandError: Decodable {
|
||||
case UNKNOWN
|
||||
case SYNTAX
|
||||
@@ -1750,3 +1858,14 @@ public enum ArchiveError: Decodable {
|
||||
case `import`(chatError: ChatError)
|
||||
case importFile(file: String, chatError: ChatError)
|
||||
}
|
||||
|
||||
public enum RemoteCtrlError: Decodable {
|
||||
case inactive
|
||||
case badState
|
||||
case busy
|
||||
case timeout
|
||||
case disconnected(remoteCtrlId: Int64, reason: String)
|
||||
case badInvitation
|
||||
case badVersion(appVersion: String)
|
||||
// case protocolError(protocolError: RemoteProtocolError)
|
||||
}
|
||||
|
||||
@@ -83,9 +83,9 @@ public enum AppState: String {
|
||||
|
||||
public var canSuspend: Bool {
|
||||
switch self {
|
||||
case .active: true
|
||||
case .bgRefresh: true
|
||||
default: false
|
||||
case .active: return true
|
||||
case .bgRefresh: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,8 +422,8 @@ public enum CustomTimeUnit {
|
||||
|
||||
|
||||
public func timeText(_ seconds: Int?) -> String {
|
||||
guard let seconds = seconds else { return "off" }
|
||||
if seconds == 0 { return "0 sec" }
|
||||
guard let seconds = seconds else { return NSLocalizedString("off", comment: "time to disappear") }
|
||||
if seconds == 0 { return NSLocalizedString("0 sec", comment: "time to disappear") }
|
||||
return CustomTimeUnit.toText(seconds: seconds)
|
||||
}
|
||||
|
||||
@@ -1370,7 +1370,7 @@ public struct Contact: Identifiable, Decodable, NamedChat {
|
||||
public var contactId: Int64
|
||||
var localDisplayName: ContactName
|
||||
public var profile: LocalProfile
|
||||
public var activeConn: Connection
|
||||
public var activeConn: Connection?
|
||||
public var viaGroup: Int64?
|
||||
public var contactUsed: Bool
|
||||
public var contactStatus: ContactStatus
|
||||
@@ -1384,10 +1384,10 @@ public struct Contact: Identifiable, Decodable, NamedChat {
|
||||
|
||||
public var id: ChatId { get { "@\(contactId)" } }
|
||||
public var apiId: Int64 { get { contactId } }
|
||||
public var ready: Bool { get { activeConn.connStatus == .ready } }
|
||||
public var ready: Bool { get { activeConn?.connStatus == .ready } }
|
||||
public var active: Bool { get { contactStatus == .active } }
|
||||
public var sendMsgEnabled: Bool { get {
|
||||
(ready && active && !(activeConn.connectionStats?.ratchetSyncSendProhibited ?? false))
|
||||
(ready && active && !(activeConn?.connectionStats?.ratchetSyncSendProhibited ?? false))
|
||||
|| nextSendGrpInv
|
||||
} }
|
||||
public var nextSendGrpInv: Bool { get { contactGroupMemberId != nil && !contactGrpInvSent } }
|
||||
@@ -1396,14 +1396,18 @@ public struct Contact: Identifiable, Decodable, NamedChat {
|
||||
public var image: String? { get { profile.image } }
|
||||
public var contactLink: String? { get { profile.contactLink } }
|
||||
public var localAlias: String { profile.localAlias }
|
||||
public var verified: Bool { activeConn.connectionCode != nil }
|
||||
public var verified: Bool { activeConn?.connectionCode != nil }
|
||||
|
||||
public var directOrUsed: Bool {
|
||||
(activeConn.connLevel == 0 && !activeConn.viaGroupLink) || contactUsed
|
||||
if let activeConn = activeConn {
|
||||
(activeConn.connLevel == 0 && !activeConn.viaGroupLink) || contactUsed
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
public var contactConnIncognito: Bool {
|
||||
activeConn.customUserProfileId != nil
|
||||
activeConn?.customUserProfileId != nil
|
||||
}
|
||||
|
||||
public func allowsFeature(_ feature: ChatFeature) -> Bool {
|
||||
@@ -1843,7 +1847,7 @@ public struct GroupMember: Identifiable, Decodable {
|
||||
public func canChangeRoleTo(groupInfo: GroupInfo) -> [GroupMemberRole]? {
|
||||
if !canBeRemoved(groupInfo: groupInfo) { return nil }
|
||||
let userRole = groupInfo.membership.memberRole
|
||||
return GroupMemberRole.allCases.filter { $0 <= userRole }
|
||||
return GroupMemberRole.allCases.filter { $0 <= userRole && $0 != .author }
|
||||
}
|
||||
|
||||
public var memberIncognito: Bool {
|
||||
@@ -1867,8 +1871,8 @@ public struct GroupMember: Identifiable, Decodable {
|
||||
)
|
||||
}
|
||||
|
||||
public struct GroupMemberSettings: Decodable {
|
||||
var showMessages: Bool
|
||||
public struct GroupMemberSettings: Codable {
|
||||
public var showMessages: Bool
|
||||
}
|
||||
|
||||
public struct GroupMemberRef: Decodable {
|
||||
@@ -1883,6 +1887,7 @@ public struct GroupMemberIds: Decodable {
|
||||
|
||||
public enum GroupMemberRole: String, Identifiable, CaseIterable, Comparable, Decodable {
|
||||
case observer = "observer"
|
||||
case author = "author"
|
||||
case member = "member"
|
||||
case admin = "admin"
|
||||
case owner = "owner"
|
||||
@@ -1892,6 +1897,7 @@ public enum GroupMemberRole: String, Identifiable, CaseIterable, Comparable, Dec
|
||||
public var text: String {
|
||||
switch self {
|
||||
case .observer: return NSLocalizedString("observer", comment: "member role")
|
||||
case .author: return NSLocalizedString("author", comment: "member role")
|
||||
case .member: return NSLocalizedString("member", comment: "member role")
|
||||
case .admin: return NSLocalizedString("admin", comment: "member role")
|
||||
case .owner: return NSLocalizedString("owner", comment: "member role")
|
||||
@@ -1901,9 +1907,10 @@ public enum GroupMemberRole: String, Identifiable, CaseIterable, Comparable, Dec
|
||||
private var comparisonValue: Int {
|
||||
switch self {
|
||||
case .observer: return 0
|
||||
case .member: return 1
|
||||
case .admin: return 2
|
||||
case .owner: return 3
|
||||
case .author: return 1
|
||||
case .member: return 2
|
||||
case .admin: return 3
|
||||
case .owner: return 4
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2090,7 +2097,7 @@ public struct ChatItem: Identifiable, Decodable {
|
||||
|
||||
public var memberConnected: GroupMember? {
|
||||
switch chatDir {
|
||||
case .groupRcv(let groupMember):
|
||||
case let .groupRcv(groupMember):
|
||||
switch content {
|
||||
case .rcvGroupEvent(rcvGroupEvent: .memberConnected): return groupMember
|
||||
default: return nil
|
||||
@@ -2099,6 +2106,35 @@ public struct ChatItem: Identifiable, Decodable {
|
||||
}
|
||||
}
|
||||
|
||||
public var mergeCategory: CIMergeCategory? {
|
||||
switch content {
|
||||
case .rcvChatFeature: .chatFeature
|
||||
case .sndChatFeature: .chatFeature
|
||||
case .rcvGroupFeature: .chatFeature
|
||||
case .sndGroupFeature: .chatFeature
|
||||
case let.rcvGroupEvent(event):
|
||||
switch event {
|
||||
case .userRole: nil
|
||||
case .userDeleted: nil
|
||||
case .groupDeleted: nil
|
||||
case .memberCreatedContact: nil
|
||||
default: .rcvGroupEvent
|
||||
}
|
||||
case let .sndGroupEvent(event):
|
||||
switch event {
|
||||
case .userRole: nil
|
||||
case .userLeft: nil
|
||||
default: .sndGroupEvent
|
||||
}
|
||||
default:
|
||||
if meta.itemDeleted == nil {
|
||||
nil
|
||||
} else {
|
||||
chatDir.sent ? .sndItemDeleted : .rcvItemDeleted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var showNtfDir: Bool {
|
||||
return !chatDir.sent
|
||||
}
|
||||
@@ -2176,7 +2212,7 @@ public struct ChatItem: Identifiable, Decodable {
|
||||
public var memberDisplayName: String? {
|
||||
get {
|
||||
if case let .groupRcv(groupMember) = chatDir {
|
||||
return groupMember.displayName
|
||||
return groupMember.chatViewName
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
@@ -2330,6 +2366,15 @@ public struct ChatItem: Identifiable, Decodable {
|
||||
}
|
||||
}
|
||||
|
||||
public enum CIMergeCategory {
|
||||
case memberConnected
|
||||
case rcvGroupEvent
|
||||
case sndGroupEvent
|
||||
case sndItemDeleted
|
||||
case rcvItemDeleted
|
||||
case chatFeature
|
||||
}
|
||||
|
||||
public enum CIDirection: Decodable {
|
||||
case directSnd
|
||||
case directRcv
|
||||
@@ -2508,11 +2553,13 @@ public enum SndCIStatusProgress: String, Decodable {
|
||||
|
||||
public enum CIDeleted: Decodable {
|
||||
case deleted(deletedTs: Date?)
|
||||
case blocked(deletedTs: Date?)
|
||||
case moderated(deletedTs: Date?, byGroupMember: GroupMember)
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case .deleted: return "deleted"
|
||||
case .blocked: return "blocked"
|
||||
case .moderated: return "moderated"
|
||||
}
|
||||
}
|
||||
@@ -2530,8 +2577,8 @@ protocol ItemContent {
|
||||
public enum CIContent: Decodable, ItemContent {
|
||||
case sndMsgContent(msgContent: MsgContent)
|
||||
case rcvMsgContent(msgContent: MsgContent)
|
||||
case sndDeleted(deleteMode: CIDeleteMode)
|
||||
case rcvDeleted(deleteMode: CIDeleteMode)
|
||||
case sndDeleted(deleteMode: CIDeleteMode) // legacy - since v4.3.0 itemDeleted field is used
|
||||
case rcvDeleted(deleteMode: CIDeleteMode) // legacy - since v4.3.0 itemDeleted field is used
|
||||
case sndCall(status: CICallStatus, duration: Int)
|
||||
case rcvCall(status: CICallStatus, duration: Int)
|
||||
case rcvIntegrityError(msgError: MsgErrorType)
|
||||
@@ -2632,6 +2679,7 @@ public enum MsgDecryptError: String, Decodable {
|
||||
case tooManySkipped
|
||||
case ratchetEarlier
|
||||
case other
|
||||
case ratchetSync
|
||||
|
||||
var text: String {
|
||||
switch self {
|
||||
@@ -2639,6 +2687,7 @@ public enum MsgDecryptError: String, Decodable {
|
||||
case .tooManySkipped: return NSLocalizedString("Permanent decryption error", comment: "message decrypt error item")
|
||||
case .ratchetEarlier: return NSLocalizedString("Decryption error", comment: "message decrypt error item")
|
||||
case .other: return NSLocalizedString("Decryption error", comment: "message decrypt error item")
|
||||
case .ratchetSync: return NSLocalizedString("Encryption re-negotiation error", comment: "message decrypt error item")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#ifndef SimpleX_h
|
||||
#define SimpleX_h
|
||||
|
||||
#endif /* SimpleX_h */
|
||||
#include "hs_init.h"
|
||||
|
||||
extern void hs_init(int argc, char **argv[]);
|
||||
|
||||
@@ -42,3 +42,5 @@ extern char *chat_encrypt_file(char *fromPath, char *toPath);
|
||||
|
||||
// chat_decrypt_file returns null-terminated string with the error message
|
||||
extern char *chat_decrypt_file(char *fromPath, char *key, char *nonce, char *toPath);
|
||||
|
||||
#endif /* SimpleX_h */
|
||||
|
||||
25
apps/ios/SimpleXChat/hs_init.c
Normal file
25
apps/ios/SimpleXChat/hs_init.c
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// hs_init.c
|
||||
// SimpleXChat
|
||||
//
|
||||
// Created by Evgeny on 22/11/2023.
|
||||
// Copyright © 2023 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
#include "hs_init.h"
|
||||
|
||||
extern void hs_init_with_rtsopts(int * argc, char **argv[]);
|
||||
|
||||
void haskell_init(void) {
|
||||
int argc = 5;
|
||||
char *argv[] = {
|
||||
"simplex",
|
||||
"+RTS", // requires `hs_init_with_rtsopts`
|
||||
"-A16m", // chunk size for new allocations
|
||||
"-H64m", // initial heap size
|
||||
"-xn", // non-moving GC
|
||||
0
|
||||
};
|
||||
char **pargv = argv;
|
||||
hs_init_with_rtsopts(&argc, &pargv);
|
||||
}
|
||||
14
apps/ios/SimpleXChat/hs_init.h
Normal file
14
apps/ios/SimpleXChat/hs_init.h
Normal file
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// hs_init.h
|
||||
// SimpleXChat
|
||||
//
|
||||
// Created by Evgeny on 22/11/2023.
|
||||
// Copyright © 2023 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef hs_init_h
|
||||
#define hs_init_h
|
||||
|
||||
void haskell_init(void);
|
||||
|
||||
#endif /* hs_init_h */
|
||||
@@ -720,21 +720,12 @@
|
||||
/* server test step */
|
||||
"Connect" = "Свързване";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect directly" = "Свързване директно";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect incognito" = "Свързване инкогнито";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"connect to SimpleX Chat developers." = "свържете се с разработчиците на SimpleX Chat.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via contact link" = "Свързване чрез линк на контакта";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via group link?" = "Свързване чрез групов линк?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via link" = "Свърване чрез линк";
|
||||
|
||||
@@ -747,6 +738,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"connected" = "свързан";
|
||||
|
||||
/* rcv group event chat item */
|
||||
"connected directly" = "свързан директно";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"connecting" = "свързване";
|
||||
|
||||
@@ -801,9 +795,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Contact already exists" = "Контактът вече съществува";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact and all messages will be deleted - this cannot be undone!" = "Контактът и всички съобщения ще бъдат изтрити - това не може да бъде отменено!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"contact has e2e encryption" = "контактът има e2e криптиране";
|
||||
|
||||
@@ -1008,9 +999,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Delete Contact" = "Изтрий контакт";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete contact?" = "Изтрий контакт?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete database" = "Изтрий базата данни";
|
||||
|
||||
@@ -1167,12 +1155,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Discover and join groups" = "Открийте и се присъединете към групи";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Display name" = "Показвано Име";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Display name:" = "Показвано име:";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Do it later" = "Отложи";
|
||||
|
||||
@@ -1377,6 +1359,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error creating group link" = "Грешка при създаване на групов линк";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error creating member contact" = "Грешка при създаване на контакт с член";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error creating profile!" = "Грешка при създаване на профил!";
|
||||
|
||||
@@ -1455,6 +1440,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error sending email" = "Грешка при изпращане на имейл";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error sending member contact invitation" = "Грешка при изпращане на съобщение за покана за контакт";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error sending message" = "Грешка при изпращане на съобщение";
|
||||
|
||||
@@ -2227,7 +2215,8 @@
|
||||
"observer" = "наблюдател";
|
||||
|
||||
/* enabled status
|
||||
group pref value */
|
||||
group pref value
|
||||
time to disappear */
|
||||
"off" = "изключено";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2308,6 +2297,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Only your contact can send voice messages." = "Само вашият контакт може да изпраща гласови съобщения.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open" = "Отвори";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open chat" = "Отвори чат";
|
||||
|
||||
@@ -2326,12 +2318,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Opening database…" = "Отваряне на база данни…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red." = "Отварянето на линка в браузъра може да намали поверителността и сигурността на връзката. Несигурните SimpleX линкове ще бъдат червени.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"or chat with the developers" = "или пишете на разработчиците";
|
||||
|
||||
/* member role */
|
||||
"owner" = "собственик";
|
||||
|
||||
@@ -2782,9 +2768,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Send delivery receipts to" = "Изпращайте потвърждениe за доставка на";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"send direct message" = "изпрати лично съобщение";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message" = "Изпрати лично съобщение";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message to connect" = "Изпрати лично съобщение за свързване";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send disappearing message" = "Изпрати изчезващо съобщение";
|
||||
|
||||
@@ -3115,9 +3107,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Криптирането работи и новото споразумение за криптиране не е необходимо. Това може да доведе до грешки при свързване!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The group is fully decentralized – it is visible only to the members." = "Групата е напълно децентрализирана – видима е само за членовете.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The hash of the previous message is different." = "Хешът на предишното съобщение е различен.";
|
||||
|
||||
@@ -3610,9 +3599,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Ще трябва да се идентифицирате, когато стартирате или възобновите приложението след 30 секунди във фонов режим.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You will join a group this link refers to and connect to its group members." = "Ще се присъедините към групата, към която този линк препраща, и ще се свържете с нейните членове.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You will still receive calls and notifications from muted profiles when they are active." = "Все още ще получавате обаждания и известия от заглушени профили, когато са активни.";
|
||||
|
||||
@@ -3643,9 +3629,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat database is not encrypted - set passphrase to encrypt it." = "Вашата чат база данни не е криптирана - задайте парола, за да я криптирате.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profile will be sent to group members" = "Вашият чат профил ще бъде изпратен на членовете на групата";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profiles" = "Вашите чат профили";
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"_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ší.";
|
||||
|
||||
/* 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!";
|
||||
|
||||
@@ -181,6 +184,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"%lld minutes" = "%lld minut";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lld new interface languages" = "%d nové jazyky rozhraní";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lld second(s)" = "%lld vteřin";
|
||||
|
||||
@@ -443,6 +449,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"App build: %@" = "Sestavení aplikace: %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App encrypts new local files (except videos)." = "Aplikace šifruje nové místní soubory (s výjimkou videí).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App icon" = "Ikona aplikace";
|
||||
|
||||
@@ -536,6 +545,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Both you and your contact can send voice messages." = "Hlasové zprávy můžete posílat vy i váš kontakt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bulharský, finský, thajský a ukrajinský - díky uživatelům a [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Podle chat profilu (výchozí) nebo [podle připojení](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).";
|
||||
|
||||
@@ -708,21 +720,12 @@
|
||||
/* server test step */
|
||||
"Connect" = "Připojit";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect directly" = "Připojit přímo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect incognito" = "Spojit se inkognito";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"connect to SimpleX Chat developers." = "připojit se k vývojářům SimpleX Chat.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via contact link" = "Připojit se přes odkaz";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via group link?" = "Připojit se přes odkaz skupiny?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via link" = "Připojte se prostřednictvím odkazu";
|
||||
|
||||
@@ -735,6 +738,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"connected" = "připojeno";
|
||||
|
||||
/* rcv group event chat item */
|
||||
"connected directly" = "připojeno přímo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"connecting" = "připojování";
|
||||
|
||||
@@ -789,9 +795,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Contact already exists" = "Kontakt již existuje";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact and all messages will be deleted - this cannot be undone!" = "Kontakt a všechny zprávy budou smazány - nelze to vzít zpět!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"contact has e2e encryption" = "kontakt má šifrování e2e";
|
||||
|
||||
@@ -843,6 +846,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Create link" = "Vytvořit odkaz";
|
||||
|
||||
/* 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";
|
||||
|
||||
@@ -993,9 +999,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Delete Contact" = "Smazat kontakt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete contact?" = "Smazat kontakt?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete database" = "Odstranění databáze";
|
||||
|
||||
@@ -1125,6 +1128,9 @@
|
||||
/* authentication reason */
|
||||
"Disable SimpleX Lock" = "Vypnutí zámku SimpleX";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"disabled" = "vypnut";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Disappearing message" = "Mizící zpráva";
|
||||
|
||||
@@ -1147,10 +1153,7 @@
|
||||
"Disconnect" = "Odpojit";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Display name" = "Zobrazované jméno";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Display name:" = "Zobrazované jméno:";
|
||||
"Discover and join groups" = "Objevte a připojte skupiny";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Do it later" = "Udělat později";
|
||||
@@ -1242,6 +1245,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Encrypt database?" = "Šifrovat databázi?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Encrypt local files" = "Šifrovat místní soubory";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Encrypt stored files & media" = "Šifrovat uložené soubory a média";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Encrypted database" = "Zašifrovaná databáze";
|
||||
|
||||
@@ -1350,9 +1359,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error creating group link" = "Chyba při vytváření odkazu skupiny";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error creating member contact" = "Chyba vytvoření kontaktu člena";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error creating profile!" = "Chyba při vytváření profilu!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error decrypting file" = "Chyba dešifrování souboru";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting chat database" = "Chyba při mazání databáze chatu";
|
||||
|
||||
@@ -1425,6 +1440,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error sending email" = "Chyba odesílání e-mailu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error sending member contact invitation" = "Chyba odeslání pozvánky kontaktu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error sending message" = "Chyba při odesílání zprávy";
|
||||
|
||||
@@ -2115,6 +2133,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"New database archive" = "Archiv nové databáze";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"New desktop app!" = "Nová desktopová aplikace!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"New display name" = "Nově zobrazované jméno";
|
||||
|
||||
@@ -2151,6 +2172,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"No contacts to add" = "Žádné kontakty k přidání";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No delivery information" = "Žádné informace o dodání";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No device token!" = "Žádný token zařízení!";
|
||||
|
||||
@@ -2188,7 +2212,8 @@
|
||||
"observer" = "pozorovatel";
|
||||
|
||||
/* enabled status
|
||||
group pref value */
|
||||
group pref value
|
||||
time to disappear */
|
||||
"off" = "vypnuto";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2269,6 +2294,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Only your contact can send voice messages." = "Hlasové zprávy může odesílat pouze váš kontakt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open" = "Otevřít";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open chat" = "Otevřete chat";
|
||||
|
||||
@@ -2287,12 +2315,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Opening database…" = "Otvírání databáze…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red." = "Otevření odkazu v prohlížeči může snížit soukromí a bezpečnost připojení. Nedůvěryhodné odkazy SimpleX budou červené.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"or chat with the developers" = "nebo chat s vývojáři";
|
||||
|
||||
/* member role */
|
||||
"owner" = "vlastník";
|
||||
|
||||
@@ -2482,6 +2504,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in our GitHub repository." = "Další informace najdete v našem repozitáři GitHub.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Receipts are disabled" = "Informace o dodání jsou zakázány";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"received answer…" = "obdržel odpověď…";
|
||||
|
||||
@@ -2740,9 +2765,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Send delivery receipts to" = "Potvrzení o doručení zasílat na";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"send direct message" = "odeslat přímou zprávu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message" = "Odeslat přímou zprávu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message to connect" = "Odeslat přímou zprávu pro připojení";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send disappearing message" = "Poslat mizící zprávu";
|
||||
|
||||
@@ -2785,9 +2816,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Sending receipts is disabled for %lld contacts" = "Odesílání potvrzení o doručení je vypnuto pro %lld kontakty";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending receipts is disabled for %lld groups" = "Odesílání potvrzení o doručení vypnuto pro %lld skupiny";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending receipts is enabled for %lld contacts" = "Odesílání potvrzení o doručení je povoleno pro %lld kontakty";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending receipts is enabled for %lld groups" = "Odesílání potvrzení o doručení povoleno pro %lld skupiny";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending via" = "Odesílání přes";
|
||||
|
||||
@@ -2872,6 +2909,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Show developer options" = "Zobrazit možnosti vývojáře";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Show last messages" = "Zobrazit poslední zprávy";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Show preview" = "Zobrazení náhledu";
|
||||
|
||||
@@ -2914,12 +2954,18 @@
|
||||
/* simplex link type */
|
||||
"SimpleX one-time invitation" = "Jednorázová pozvánka SimpleX";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Simplified incognito mode" = "Zjednodušený inkognito režim";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Skip" = "Přeskočit";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Skipped messages" = "Přeskočené zprávy";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Small groups (max 20)" = "Malé skupiny (max. 20)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"SMP servers" = "SMP servery";
|
||||
|
||||
@@ -3058,9 +3104,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Šifrování funguje a nové povolení šifrování není vyžadováno. To může vyvolat chybu v připojení!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The group is fully decentralized – it is visible only to the members." = "Skupina je plně decentralizovaná - je viditelná pouze pro členy.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The hash of the previous message is different." = "Hash předchozí zprávy se liší.";
|
||||
|
||||
@@ -3118,6 +3161,9 @@
|
||||
/* notification title */
|
||||
"this contact" = "tento kontakt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"This group has over %lld members, delivery receipts are not sent." = "Tato skupina má více než %lld členů, potvrzení o doručení nejsou odesílány.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"This group no longer exists." = "Tato skupina již neexistuje.";
|
||||
|
||||
@@ -3154,6 +3200,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Chcete-li ověřit koncové šifrování u svého kontaktu, porovnejte (nebo naskenujte) kód na svých zařízeních.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Toggle incognito when connecting." = "Změnit inkognito režim při připojení.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Transport isolation" = "Izolace transportu";
|
||||
|
||||
@@ -3262,12 +3311,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Use chat" = "Použijte chat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use current profile" = "Použít aktuální profil";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use for new connections" = "Použít pro nová připojení";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use iOS call interface" = "Použít rozhraní volání iOS";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use new incognito profile" = "Použít nový inkognito profil";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use server" = "Použít server";
|
||||
|
||||
@@ -3541,9 +3596,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Při spuštění nebo obnovení aplikace po 30 sekundách na pozadí budete požádáni o ověření.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You will join a group this link refers to and connect to its group members." = "Připojíte se ke skupině, na kterou tento odkaz odkazuje, a spojíte se s jejími členy.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You will still receive calls and notifications from muted profiles when they are active." = "Stále budete přijímat volání a upozornění od umlčených profilů pokud budou aktivní.";
|
||||
|
||||
@@ -3574,9 +3626,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat database is not encrypted - set passphrase to encrypt it." = "Vaše chat databáze není šifrována – nastavte přístupovou frázi pro její šifrování.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profile will be sent to group members" = "Váš chat profil bude zaslán členům skupiny";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profiles" = "Vaše chat profily";
|
||||
|
||||
@@ -3610,6 +3659,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Your privacy" = "Vaše soukromí";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your profile **%@** will be shared." = "Váš profil **%@** bude sdílen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Váš profil je uložen ve vašem zařízení a sdílen pouze s vašimi kontakty.\nServery SimpleX nevidí váš profil.";
|
||||
|
||||
|
||||
@@ -19,9 +19,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"_italic_" = "\\_kursiv_";
|
||||
|
||||
/* 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." = "- Verbinden mit dem [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- Empfangsbestätigungen (für bis zu 20 Mitglieder).\n- Schneller und stabiler.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"- more stable message delivery.\n- a bit better groups.\n- and more!" = "- stabilere Zustellung von Nachrichten.\n- ein bisschen verbesserte Gruppen.\n- und mehr!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"- 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.";
|
||||
|
||||
@@ -40,6 +46,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"(" = "(";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"(new)" = "(Neu)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"(this device v%@)" = "(Dieses Gerät hat v%@)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
")" = ")";
|
||||
|
||||
@@ -116,11 +128,17 @@
|
||||
"%@ %@" = "%@ %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%@ and %@ connected" = "%@ und %@ wurden verbunden";
|
||||
"%@ and %@" = "%@ und %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%@ and %@ connected" = "%@ und %@ wurden mit Ihnen verbunden";
|
||||
|
||||
/* copied message info, <sender> at <time> */
|
||||
"%@ at %@:" = "%1$@ an %2$@:";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%@ connected" = "%@ wurde mit Ihnen verbunden";
|
||||
|
||||
/* notification title */
|
||||
"%@ is connected!" = "%@ ist mit Ihnen verbunden!";
|
||||
|
||||
@@ -137,7 +155,10 @@
|
||||
"%@ wants to connect!" = "%@ will sich mit Ihnen verbinden!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%@, %@ and %lld other members connected" = "%@, %@ und %lld weitere Mitglieder wurden verbunden";
|
||||
"%@, %@ and %lld members" = "%@, %@ und %lld Mitglieder";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%@, %@ and %lld other members connected" = "%@, %@ und %lld weitere Mitglieder wurden mit Ihnen verbunden";
|
||||
|
||||
/* copied message info */
|
||||
"%@:" = "%@:";
|
||||
@@ -175,12 +196,27 @@
|
||||
/* No comment provided by engineer. */
|
||||
"%lld file(s) with total size of %@" = "%lld Datei(en) mit einem Gesamtspeicherverbrauch von %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lld group events" = "%lld Gruppenereignisse";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lld members" = "%lld Mitglieder";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lld messages blocked" = "%lld Nachrichten blockiert";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lld messages marked deleted" = "%lld Nachrichten als gelöscht markiert";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lld messages moderated by %@" = "%lld Nachrichten von %@ moderiert";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lld minutes" = "%lld Minuten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lld new interface languages" = "%lld neue Sprachen für die Bedienoberfläche";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lld second(s)" = "%lld Sekunde(n)";
|
||||
|
||||
@@ -223,6 +259,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"~strike~" = "\\~durchstreichen~";
|
||||
|
||||
/* time to disappear */
|
||||
"0 sec" = "0 sek";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"0s" = "0s";
|
||||
|
||||
@@ -365,6 +404,9 @@
|
||||
/* 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!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"All your contacts will remain connected." = "Alle Ihre Kontakte bleiben verbunden.";
|
||||
|
||||
@@ -428,6 +470,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Already connected?" = "Sind Sie bereits verbunden?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Already connecting!" = "Bereits verbunden!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Already joining the group!" = "Sie sind bereits Mitglied der Gruppe!";
|
||||
|
||||
/* pref value */
|
||||
"always" = "Immer";
|
||||
|
||||
@@ -437,12 +485,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"An empty chat profile with the provided name is created, and the app opens as usual." = "Es wurde ein leeres Chat-Profil mit dem eingegebenen Namen erstellt und die App öffnet wie gewohnt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"and %lld other events" = "und %lld weitere Ereignisse";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Answer call" = "Anruf annehmen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App build: %@" = "App Build: %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App encrypts new local files (except videos)." = "Neue lokale Dateien (außer Video-Dateien) werden von der App verschlüsselt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App icon" = "App-Icon";
|
||||
|
||||
@@ -491,6 +545,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Authentication unavailable" = "Authentifizierung nicht verfügbar";
|
||||
|
||||
/* member role */
|
||||
"author" = "Autor";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Auto-accept" = "Automatisch akzeptieren";
|
||||
|
||||
@@ -503,6 +560,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Back" = "Zurück";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Bad desktop address" = "Falsche Desktop-Adresse";
|
||||
|
||||
/* integrity error chat item */
|
||||
"bad message hash" = "Ungültiger Nachrichten-Hash";
|
||||
|
||||
@@ -515,9 +575,27 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Bad message ID" = "Falsche Nachrichten-ID";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Better groups" = "Bessere Gruppen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Better messages" = "Verbesserungen bei Nachrichten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Block" = "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?" = "Mitglied blockieren?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"blocked" = "blockiert";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"bold" = "fett";
|
||||
|
||||
@@ -536,6 +614,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Both you and your contact can send voice messages." = "Sowohl Ihr Kontakt, als auch Sie können Sprachnachrichten senden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bulgarisch, Finnisch, Thailändisch und Ukrainisch - Dank der Nutzer und [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Per Chat-Profil (Voreinstellung) oder [per Verbindung](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).";
|
||||
|
||||
@@ -709,19 +790,28 @@
|
||||
"Connect" = "Verbinden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect directly" = "Direkt verbinden";
|
||||
"Connect automatically" = "Automatisch verbinden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect incognito" = "Inkognito verbinden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect to desktop" = "Mit dem Desktop verbinden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"connect to SimpleX Chat developers." = "Mit den SimpleX Chat-Entwicklern verbinden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via contact link" = "Über den Kontakt-Link verbinden";
|
||||
"Connect to yourself?" = "Mit Ihnen selbst verbinden?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via group link?" = "Über den Gruppen-Link verbinden?";
|
||||
"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!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via contact address" = "Über die Kontakt-Adresse verbinden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via link" = "Über einen Link verbinden";
|
||||
@@ -732,9 +822,21 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via one-time link" = "Über einen Einmal-Link verbinden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect with %@" = "Mit %@ verbinden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"connected" = "Verbunden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connected desktop" = "Verbundener Desktop";
|
||||
|
||||
/* rcv group event chat item */
|
||||
"connected directly" = "Direkt miteinander verbunden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connected to desktop" = "Mit dem Desktop verbunden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"connecting" = "verbinde";
|
||||
|
||||
@@ -759,6 +861,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Connecting server… (error: %@)" = "Mit dem Server verbinden… (Fehler: %@)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connecting to desktop" = "Mit dem Desktop verbinden";
|
||||
|
||||
/* chat list item title */
|
||||
"connecting…" = "Verbinde…";
|
||||
|
||||
@@ -777,6 +882,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Connection request sent!" = "Verbindungsanfrage wurde gesendet!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connection terminated" = "Verbindung beendet";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connection timeout" = "Verbindungszeitüberschreitung";
|
||||
|
||||
@@ -789,9 +897,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Contact already exists" = "Der Kontakt ist bereits vorhanden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact and all messages will be deleted - this cannot be undone!" = "Der Kontakt und alle Nachrichten werden gelöscht - dies kann nicht rückgängig gemacht werden!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"contact has e2e encryption" = "Kontakt nutzt E2E-Verschlüsselung";
|
||||
|
||||
@@ -828,24 +933,39 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Core version: v%@" = "Core Version: v%@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Correct name to %@?" = "Richtiger Name für %@?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create" = "Erstellen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create a group using a random profile." = "Erstellen Sie eine Gruppe mit einem zufälligen Profil.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create an address to let people connect with you." = "Erstellen Sie eine Adresse, damit sich Personen mit Ihnen verbinden können.";
|
||||
|
||||
/* server test step */
|
||||
"Create file" = "Datei erstellen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create group" = "Gruppe erstellen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create group link" = "Gruppenlink erstellen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create link" = "Link erzeugen";
|
||||
|
||||
/* 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";
|
||||
|
||||
/* server test step */
|
||||
"Create queue" = "Erzeuge Warteschlange";
|
||||
|
||||
@@ -960,6 +1080,9 @@
|
||||
/* chat item action */
|
||||
"Delete" = "Löschen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete %lld messages?" = "%lld Nachrichten löschen?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete address" = "Adresse löschen";
|
||||
|
||||
@@ -972,6 +1095,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Delete all files" = "Alle Dateien löschen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete and notify contact" = "Kontakt löschen und benachrichtigen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete archive" = "Archiv löschen";
|
||||
|
||||
@@ -994,7 +1120,7 @@
|
||||
"Delete Contact" = "Kontakt löschen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete contact?" = "Kontakt löschen?";
|
||||
"Delete contact?\nThis cannot be undone!" = "Kontakt löschen?\nDas kann nicht rückgängig gemacht werden!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete database" = "Datenbank löschen";
|
||||
@@ -1071,6 +1197,9 @@
|
||||
/* copied message info */
|
||||
"Deleted at: %@" = "Gelöscht um: %@";
|
||||
|
||||
/* rcv direct event chat item */
|
||||
"deleted contact" = "Gelöschter Kontakt";
|
||||
|
||||
/* rcv group event chat item */
|
||||
"deleted group" = "Gruppe gelöscht";
|
||||
|
||||
@@ -1086,6 +1215,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Description" = "Beschreibung";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Desktop address" = "Desktop-Adresse";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Desktop app version %@ is not compatible with this app." = "Desktop App-Version %@ ist mit dieser App nicht kompatibel.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Desktop devices" = "Desktop-Geräte";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Develop" = "Entwicklung";
|
||||
|
||||
@@ -1150,10 +1288,13 @@
|
||||
"Disconnect" = "Trennen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Display name" = "Angezeigter Name";
|
||||
"Disconnect desktop?" = "Desktop-Verbindung trennen?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Display name:" = "Angezeigter Name:";
|
||||
"Discover and join groups" = "Gruppen entdecken und ihnen beitreten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Discover via local network" = "Lokales Netzwerk durchsuchen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Do it later" = "Später wiederholen";
|
||||
@@ -1248,6 +1389,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Encrypt local files" = "Lokale Dateien verschlüsseln";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Encrypt stored files & media" = "Gespeicherte Dateien & Medien verschlüsseln";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Encrypted database" = "Verschlüsselte Datenbank";
|
||||
|
||||
@@ -1287,6 +1431,12 @@
|
||||
/* chat item text */
|
||||
"encryption re-negotiation allowed for %@" = "Neuaushandlung der Verschlüsselung von %@ erlaubt";
|
||||
|
||||
/* message decrypt error item */
|
||||
"Encryption re-negotiation error" = "Fehler bei der Neuverhandlung der Verschlüsselung";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Encryption re-negotiation failed." = "Neuverhandlung der Verschlüsselung fehlgeschlagen.";
|
||||
|
||||
/* chat item text */
|
||||
"encryption re-negotiation required" = "Neuaushandlung der Verschlüsselung notwendig";
|
||||
|
||||
@@ -1302,6 +1452,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Enter correct passphrase." = "Geben Sie das korrekte Passwort ein.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enter group name…" = "Geben Sie den Gruppennamen ein…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enter Passcode" = "Zugangscode eingeben";
|
||||
|
||||
@@ -1314,12 +1467,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Enter server manually" = "Geben Sie den Server manuell ein";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enter this device name…" = "Geben Sie diesen Gerätenamen ein…";
|
||||
|
||||
/* placeholder */
|
||||
"Enter welcome message…" = "Geben Sie eine Begrüßungsmeldung ein …";
|
||||
|
||||
/* placeholder */
|
||||
"Enter welcome message… (optional)" = "Geben Sie eine Begrüßungsmeldung ein … (optional)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enter your name…" = "Geben Sie Ihren Namen ein…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"error" = "Fehler";
|
||||
|
||||
@@ -1356,6 +1515,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error creating group link" = "Fehler beim Erzeugen des Gruppen-Links";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error creating member contact" = "Fehler beim Anlegen eines Mitglied-Kontaktes";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error creating profile!" = "Fehler beim Erstellen des Profils!";
|
||||
|
||||
@@ -1434,6 +1596,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error sending email" = "Fehler beim Senden der eMail";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error sending member contact invitation" = "Fehler beim Senden einer Mitglied-Kontakt-Einladung";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error sending message" = "Fehler beim Senden der Nachricht";
|
||||
|
||||
@@ -1485,6 +1650,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Exit without saving" = "Beenden ohne Speichern";
|
||||
|
||||
/* chat item action */
|
||||
"Expand" = "Erweitern";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Export database" = "Datenbank exportieren";
|
||||
|
||||
@@ -1503,6 +1671,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Fast and no wait until the sender is online!" = "Schnell und ohne warten auf den Absender, bis er online ist!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Faster joining and more reliable messages." = "Schnellerer Gruppenbeitritt und zuverlässigere Nachrichtenzustellung.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Favorite" = "Favorit";
|
||||
|
||||
@@ -1560,6 +1731,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"For console" = "Für Konsole";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Found desktop" = "Gefundener Desktop";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"French interface" = "Französische Bedienoberfläche";
|
||||
|
||||
@@ -1572,6 +1746,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Full name:" = "Vollständiger Name:";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Fully decentralized – visible only to members." = "Vollständig dezentralisiert – nur für Mitglieder sichtbar.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Fully re-implemented - work in background!" = "Komplett neu umgesetzt - arbeitet nun im Hintergrund!";
|
||||
|
||||
@@ -1584,6 +1761,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Group" = "Gruppe";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group already exists" = "Die Gruppe besteht bereits";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group already exists!" = "Die Gruppe besteht bereits!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"group deleted" = "Gruppe gelöscht";
|
||||
|
||||
@@ -1755,6 +1938,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Incognito" = "Inkognito";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Incognito groups" = "Inkognito-Gruppen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Incognito mode" = "Inkognito-Modus";
|
||||
|
||||
@@ -1782,6 +1968,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Incompatible database version" = "Inkompatible Datenbank-Version";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Incompatible version" = "Inkompatible Version";
|
||||
|
||||
/* PIN entry */
|
||||
"Incorrect passcode" = "Zugangscode ist falsch";
|
||||
|
||||
@@ -1821,6 +2010,9 @@
|
||||
/* invalid chat item */
|
||||
"invalid data" = "Ungültige Daten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid name!" = "Ungültiger Name!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Invalid server address!" = "Ungültige Serveradresse!";
|
||||
|
||||
@@ -1879,7 +2071,7 @@
|
||||
"It can happen when:\n1. The messages expired in the sending client after 2 days or on the server after 30 days.\n2. Message decryption failed, because you or your contact used old database backup.\n3. The connection was compromised." = "Dies kann unter folgenden Umständen passieren:\n1. Die Nachrichten verfallen auf dem sendenden Client-System nach 2 Tagen oder auf dem Server nach 30 Tagen.\n2. Die Nachrichten-Entschlüsselung ist fehlgeschlagen, da von Ihnen oder Ihrem Kontakt ein altes Datenbank-Backup genutzt wurde.\n3. Die Verbindung wurde kompromittiert.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"It seems like you are already connected via this link. If it is not the case, there was an error (%@)." = "Es sieht so aus, dass Sie bereits über diesen Link verbunden sind. Wenn das nicht der Fall, gab es einen Fehler (%@).";
|
||||
"It seems like you are already connected via this link. If it is not the case, there was an error (%@)." = "Es sieht so aus, als ob Sie bereits über diesen Link verbunden sind. Wenn das nicht der Fall ist, gab es einen Fehler (%@).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Italian interface" = "Italienische Bedienoberfläche";
|
||||
@@ -1899,12 +2091,24 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Join group" = "Treten Sie der Gruppe bei";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Join group?" = "Der Gruppe beitreten?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Join incognito" = "Inkognito beitreten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Join with current profile" = "Mit dem aktuellen Profil beitreten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Join your group?\nThis is your link for group %@!" = "Ihrer Gruppe beitreten?\nDas ist Ihr Link für die Gruppe %@!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Joining group" = "Der Gruppe beitreten";
|
||||
|
||||
/* 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 your connections" = "Ihre Verbindungen beibehalten";
|
||||
|
||||
@@ -1941,6 +2145,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Limitations" = "Einschränkungen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Link mobile and desktop apps! 🔗" = "Verknüpfe Mobiltelefon- und Desktop-Apps! 🔗";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Linked desktop options" = "Verknüpfte Desktop-Optionen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Linked desktops" = "Verknüpfte Desktops";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"LIVE" = "LIVE";
|
||||
|
||||
@@ -2046,6 +2259,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Messages & files" = "Nachrichten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Messages from %@ will be shown!" = "Die Nachrichten von %@ werden angezeigt!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrating database archive…" = "Datenbank-Archiv wird migriert…";
|
||||
|
||||
@@ -2127,6 +2343,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"New database archive" = "Neues Datenbankarchiv";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"New desktop app!" = "Neue Desktop-App!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"New display name" = "Neuer Anzeigename";
|
||||
|
||||
@@ -2190,6 +2409,9 @@
|
||||
/* copied message info in history */
|
||||
"no text" = "Kein Text";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Not compatible!" = "Nicht kompatibel!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Notifications" = "Benachrichtigungen";
|
||||
|
||||
@@ -2203,7 +2425,8 @@
|
||||
"observer" = "Beobachter";
|
||||
|
||||
/* enabled status
|
||||
group pref value */
|
||||
group pref value
|
||||
time to disappear */
|
||||
"off" = "Aus";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2284,12 +2507,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Only your contact can send voice messages." = "Nur Ihr Kontakt kann Sprachnachrichten versenden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open" = "Öffnen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open chat" = "Chat öffnen";
|
||||
|
||||
/* authentication reason */
|
||||
"Open chat console" = "Chat-Konsole öffnen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open group" = "Gruppe öffnen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open Settings" = "Geräte-Einstellungen öffnen";
|
||||
|
||||
@@ -2302,12 +2531,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Opening database…" = "Öffne Datenbank …";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red." = "Das Öffnen des Links über den Browser kann die Privatsphäre und Sicherheit der Verbindung reduzieren. SimpleX-Links, denen nicht vertraut wird, werden Rot sein.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"or chat with the developers" = "oder chatten Sie mit den Entwicklern";
|
||||
|
||||
/* member role */
|
||||
"owner" = "Eigentümer";
|
||||
|
||||
@@ -2332,6 +2555,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Paste" = "Einfügen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Paste desktop address" = "Desktop-Adresse einfügen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Paste image" = "Bild einfügen";
|
||||
|
||||
@@ -2428,6 +2654,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Profile image" = "Profilbild";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Profile name" = "Profilname";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Profile name:" = "Profilname:";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Profile password" = "Passwort für Profil";
|
||||
|
||||
@@ -2593,6 +2825,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Renegotiate encryption?" = "Verschlüsselung neu aushandeln?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Repeat connection request?" = "Verbindungsanfrage wiederholen?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Repeat join request?" = "Verbindungsanfrage wiederholen?";
|
||||
|
||||
/* chat item action */
|
||||
"Reply" = "Antwort";
|
||||
|
||||
@@ -2704,6 +2942,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Scan QR code" = "QR-Code scannen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Scan QR code from desktop" = "Den QR-Code vom Desktop scannen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Scan security code from your contact's app." = "Scannen Sie den Sicherheitscode von der App Ihres Kontakts.";
|
||||
|
||||
@@ -2758,9 +2999,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Send delivery receipts to" = "Empfangsbestätigungen senden an";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"send direct message" = "Direktnachricht senden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message" = "Direktnachricht senden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message to connect" = "Eine Direktnachricht zum Verbinden senden";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send disappearing message" = "Verschwindende Nachricht senden";
|
||||
|
||||
@@ -2842,6 +3089,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Servers" = "Server";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Session code" = "Sitzungscode";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Set 1 day" = "Einen Tag festlegen";
|
||||
|
||||
@@ -2941,6 +3191,9 @@
|
||||
/* simplex link type */
|
||||
"SimpleX one-time invitation" = "SimpleX-Einmal-Einladung";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Simplified incognito mode" = "Vereinfachter Inkognito-Modus";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Skip" = "Überspringen";
|
||||
|
||||
@@ -3025,6 +3278,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to activate profile." = "Tippen Sie auf das Profil um es zu aktivieren.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to Connect" = "Zum Verbinden antippen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to join" = "Zum Beitreten tippen";
|
||||
|
||||
@@ -3088,9 +3344,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Die Verschlüsselung funktioniert und ein neues Verschlüsselungsabkommen ist nicht erforderlich. Es kann zu Verbindungsfehlern kommen!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The group is fully decentralized – it is visible only to the members." = "Die Gruppe ist vollständig dezentralisiert – sie ist nur für Mitglieder sichtbar.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The hash of the previous message is different." = "Der Hash der vorherigen Nachricht unterscheidet sich.";
|
||||
|
||||
@@ -3148,12 +3401,21 @@
|
||||
/* notification title */
|
||||
"this contact" = "Dieser Kontakt";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"This device name" = "Dieser Gerätename";
|
||||
|
||||
/* 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.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"This group no longer exists." = "Diese Gruppe existiert nicht mehr.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"This is your own one-time link!" = "Das ist Ihr eigener Einmal-Link!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"This is your own SimpleX address!" = "Das ist Ihre eigene SimpleX-Adresse!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"This setting applies to messages in your current chat profile **%@**." = "Diese Einstellung gilt für Nachrichten in Ihrem aktuellen Chat-Profil **%@**.";
|
||||
|
||||
@@ -3163,6 +3425,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"To connect, your contact can scan QR code or use the link in the app." = "Um eine Verbindung herzustellen, kann Ihr Kontakt den QR-Code scannen oder den Link in der App verwenden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"To hide unwanted messages." = "Um unerwünschte Nachrichten zu verbergen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"To make a new connection" = "Um eine Verbindung mit einem neuen Kontakt zu erstellen";
|
||||
|
||||
@@ -3179,7 +3444,7 @@
|
||||
"To record voice message please grant permission to use Microphone." = "Bitte erlauben Sie die Nutzung des Mikrofons, um Sprachnachrichten aufnehmen zu können.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page." = "Geben Sie ein vollständiges Passwort in das Suchfeld auf der Seite **Meine Chat-Profile** ein, um Ihr verborgenes Profil zu sehen.";
|
||||
"To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page." = "Geben Sie ein vollständiges Passwort in das Suchfeld auf der Seite **Ihre Chat-Profile** ein, um Ihr verborgenes Profil zu sehen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"To support instant push notifications the chat database has to be migrated." = "Um sofortige Push-Benachrichtigungen zu unterstützen, muss die Chat-Datenbank migriert werden.";
|
||||
@@ -3187,6 +3452,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Um die Ende-zu-Ende-Verschlüsselung mit Ihrem Kontakt zu überprüfen, müssen Sie den Sicherheitscode in Ihren Apps vergleichen oder scannen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Toggle incognito when connecting." = "Inkognito beim Verbinden einschalten.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Transport isolation" = "Transport-Isolation";
|
||||
|
||||
@@ -3208,6 +3476,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Unable to record voice message" = "Die Aufnahme einer Sprachnachricht ist nicht möglich";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unblock" = "Freigeben";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unblock member" = "Mitglied freigeben";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unblock member?" = "Mitglied freigeben?";
|
||||
|
||||
/* item status description */
|
||||
"Unexpected error: %@" = "Unerwarteter Fehler: %@";
|
||||
|
||||
@@ -3247,6 +3524,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection." = "Entweder hat Ihr Kontakt die Verbindung gelöscht, oder dieser Link wurde bereits verwendet, es könnte sich um einen Fehler handeln - Bitte melden Sie es uns.\nBitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um sich neu verbinden zu können und stellen Sie sicher, dass Sie eine stabile Netzwerk-Verbindung haben.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unlink" = "Entkoppeln";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unlink desktop?" = "Desktop entkoppeln?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Unlock" = "Entsperren";
|
||||
|
||||
@@ -3301,6 +3584,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Use for new connections" = "Für neue Verbindungen nutzen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use from desktop" = "Vom Desktop aus nutzen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use iOS call interface" = "iOS Anrufschnittstelle nutzen";
|
||||
|
||||
@@ -3322,12 +3608,24 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Using SimpleX Chat servers." = "Verwendung von SimpleX-Chat-Servern.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"v%@" = "v%@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"v%@ (%@)" = "v%@ (%@)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Verify code with desktop" = "Code mit dem Desktop überprüfen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Verify connection" = "Verbindung überprüfen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Verify connection security" = "Sicherheit der Verbindung überprüfen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Verify connections" = "Verbindungen überprüfen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Verify security code" = "Sicherheitscode überprüfen";
|
||||
|
||||
@@ -3346,6 +3644,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"via relay" = "über Relais";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Via secure quantum resistant protocol." = "Über ein sicheres quantenbeständiges Protokoll.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Video call" = "Videoanruf";
|
||||
|
||||
@@ -3385,6 +3686,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"waiting for confirmation…" = "Warten auf Bestätigung…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Waiting for desktop..." = "Es wird auf den Desktop gewartet...";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Waiting for file" = "Warte auf Datei";
|
||||
|
||||
@@ -3440,7 +3744,7 @@
|
||||
"yes" = "Ja";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You" = "Meine Daten";
|
||||
"You" = "Ihre Daten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You accepted connection" = "Sie haben die Verbindung akzeptiert";
|
||||
@@ -3454,6 +3758,27 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You are already connected to %@." = "Sie sind bereits mit %@ verbunden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You are already connecting to %@." = "Sie sind bereits mit %@ verbunden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You are already connecting via this one-time link!" = "Sie sind bereits über diesen Einmal-Link verbunden!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You are already in group %@." = "Sie sind bereits Mitglied der Gruppe %@.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You are already joining the group %@." = "Sie sind bereits Mitglied der Gruppe %@.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You are already joining the group via this link!" = "Sie sind über diesen Link bereits Mitglied der Gruppe!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You are already joining the group via this link." = "Sie sind über diesen Link bereits Mitglied der Gruppe.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You are already joining the group!\nRepeat join request?" = "Sie sind bereits Mitglied dieser Gruppe!\nVerbindungsanfrage wiederholen?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You are connected to the server used to receive messages from this contact." = "Sie sind mit dem Server verbunden, der für den Empfang von Nachrichten mit diesem Kontakt genutzt wird.";
|
||||
|
||||
@@ -3529,6 +3854,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You could not be verified; please try again." = "Sie konnten nicht überprüft werden; bitte versuchen Sie es erneut.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have already requested connection via this address!" = "Sie haben über diese Adresse bereits eine Verbindung beantragt!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have already requested connection!\nRepeat connection request?" = "Sie haben bereits ein Verbindungsanfrage beantragt!\nVerbindungsanfrage wiederholen?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have no chats" = "Sie haben keine Chats";
|
||||
|
||||
@@ -3571,6 +3902,9 @@
|
||||
/* 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!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You will be connected when group link host's device is online, please wait or check later!" = "Sie werden verbunden, sobald das Endgerät des Gruppenlink-Hosts online ist. Bitte warten oder schauen Sie später nochmal nach!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You will be connected when your connection request is accepted, please wait or check later!" = "Sie werden verbunden, sobald Ihre Verbindungsanfrage akzeptiert wird. Bitte warten oder schauen Sie später nochmal nach!";
|
||||
|
||||
@@ -3581,7 +3915,7 @@
|
||||
"You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Sie müssen sich authentifizieren, wenn Sie die im Hintergrund befindliche App nach 30 Sekunden starten oder fortsetzen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You will join a group this link refers to and connect to its group members." = "Sie werden der Gruppe beitreten, auf die sich dieser Link bezieht und sich mit deren Gruppenmitgliedern verbinden.";
|
||||
"You will connect to all group members." = "Sie werden mit allen Gruppenmitgliedern verbunden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"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.";
|
||||
@@ -3614,10 +3948,7 @@
|
||||
"Your chat database is not encrypted - set passphrase to encrypt it." = "Ihre Chat-Datenbank ist nicht verschlüsselt. Bitte legen Sie ein Passwort fest, um sie zu schützen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profile will be sent to group members" = "Ihr Chat-Profil wird an Gruppenmitglieder gesendet";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profiles" = "Meine Chat-Profile";
|
||||
"Your chat profiles" = "Ihre Chat-Profile";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your contact needs to be online for the connection to complete.\nYou can cancel this connection and remove the contact (and try later with a new link)." = "Damit die Verbindung hergestellt werden kann, muss Ihr Kontakt online sein.\nSie können diese Verbindung abbrechen und den Kontakt entfernen (und es später nochmals mit einem neuen Link versuchen).";
|
||||
@@ -3644,10 +3975,13 @@
|
||||
"Your ICE servers" = "Ihre ICE-Server";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your preferences" = "Meine Präferenzen";
|
||||
"Your preferences" = "Ihre Präferenzen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your privacy" = "Meine Privatsphäre";
|
||||
"Your privacy" = "Ihre Privatsphäre";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your profile" = "Mein Profil";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your profile **%@** will be shared." = "Ihr Profil **%@** wird geteilt.";
|
||||
@@ -3668,10 +4002,10 @@
|
||||
"Your server address" = "Ihre Serveradresse";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your settings" = "Meine Einstellungen";
|
||||
"Your settings" = "Ihre Einstellungen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your SimpleX address" = "Meine SimpleX-Adresse";
|
||||
"Your SimpleX address" = "Ihre SimpleX-Adresse";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your SMP servers" = "Ihre SMP-Server";
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
/* Privacy - Face ID Usage Description */
|
||||
"NSFaceIDUsageDescription" = "Face ID wird von SimpleX für die lokale Authentifizierung genutzt";
|
||||
|
||||
/* Privacy - Local Network Usage Description */
|
||||
"NSLocalNetworkUsageDescription" = "SimpleX nutzt den lokalen Netzwerkzugriff, um die Nutzung von Benutzer-Chatprofilen über eine Desktop-App im gleichen Netzwerk zu erlauben.";
|
||||
|
||||
/* Privacy - Microphone Usage Description */
|
||||
"NSMicrophoneUsageDescription" = "SimpleX benötigt Zugriff auf das Mikrofon, um Audio- und Videoanrufe und die Aufnahme von Sprachnachrichten zu ermöglichen.";
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"_italic_" = "\\_italic_";
|
||||
|
||||
/* 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." = "- conexión al [servicio de directorio](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- confirmaciones de entrega (hasta 20 miembros).\n- mayor rapidez y estabilidad.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"- more stable message delivery.\n- a bit better groups.\n- and more!" = "- entrega de mensajes más estable.\n- grupos un poco mejores.\n- ¡y más!";
|
||||
|
||||
@@ -181,6 +184,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"%lld minutes" = "%lld minutos";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lld new interface languages" = "%lld idiomas de interfaz nuevos";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%lld second(s)" = "%lld segundo(s)";
|
||||
|
||||
@@ -209,10 +215,10 @@
|
||||
"%lldw" = "%lldw";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%u messages failed to decrypt." = "%u mensajes no pudieron ser descifrados.";
|
||||
"%u messages failed to decrypt." = "%u mensaje(s) no ha(n) podido ser descifrado(s).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"%u messages skipped." = "%u mensajes omitidos.";
|
||||
"%u messages skipped." = "%u mensaje(s) omitido(s).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"`a + b`" = "\\`a + b`";
|
||||
@@ -443,6 +449,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"App build: %@" = "Compilación app: %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App encrypts new local files (except videos)." = "Cifrado de los nuevos archivos locales (excepto vídeos).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App icon" = "Icono aplicación";
|
||||
|
||||
@@ -536,6 +545,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Both you and your contact can send voice messages." = "Tanto tú como tu contacto podéis enviar mensajes de voz.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Búlgaro, Finlandés, Tailandés y Ucraniano - gracias a los usuarios y [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Mediante perfil (por defecto) o [por conexión](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).";
|
||||
|
||||
@@ -708,21 +720,12 @@
|
||||
/* server test step */
|
||||
"Connect" = "Conectar";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect directly" = "Conectar directamente";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect incognito" = "Conectar incognito";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"connect to SimpleX Chat developers." = "contacta con los desarrolladores de SimpleX Chat.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via contact link" = "Conectar mediante enlace de contacto";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via group link?" = "¿Conectar mediante enlace de grupo?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Connect via link" = "Conectar mediante enlace";
|
||||
|
||||
@@ -735,6 +738,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"connected" = "conectado";
|
||||
|
||||
/* rcv group event chat item */
|
||||
"connected directly" = "conectado directamente";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"connecting" = "conectando";
|
||||
|
||||
@@ -789,9 +795,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Contact already exists" = "El contácto ya existe";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contact and all messages will be deleted - this cannot be undone!" = "El contacto y todos los mensajes serán eliminados. ¡No podrá deshacerse!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"contact has e2e encryption" = "el contacto dispone de cifrado de extremo a extremo";
|
||||
|
||||
@@ -843,6 +846,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Create link" = "Crear enlace";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" = "Crea perfil nuevo en la [aplicación para PC](https://simplex.Descargas/de chat/). 💻";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create one-time invitation link" = "Crea enlace de invitación de un uso";
|
||||
|
||||
@@ -993,9 +999,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Delete Contact" = "Eliminar contacto";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete contact?" = "Eliminar contacto?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Delete database" = "Eliminar base de datos";
|
||||
|
||||
@@ -1150,10 +1153,7 @@
|
||||
"Disconnect" = "Desconectar";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Display name" = "Nombre mostrado";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Display name:" = "Nombre mostrado:";
|
||||
"Discover and join groups" = "Descubre y únete a grupos";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Do it later" = "Hacer más tarde";
|
||||
@@ -1245,6 +1245,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Encrypt database?" = "¿Cifrar base de datos?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Encrypt local files" = "Cifra archivos locales";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Encrypt stored files & media" = "Cifra archivos almacenados y multimedia";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Encrypted database" = "Base de datos cifrada";
|
||||
|
||||
@@ -1353,9 +1359,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error creating group link" = "Error al crear enlace de grupo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error creating member contact" = "Error al establecer contacto con el miembro";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error creating profile!" = "¡Error al crear perfil!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error decrypting file" = "Error al descifrar el archivo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error deleting chat database" = "Error al eliminar base de datos";
|
||||
|
||||
@@ -1428,6 +1440,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Error sending email" = "Error al enviar email";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error sending member contact invitation" = "Error al enviar mensaje de invitación al contacto";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Error sending message" = "Error al enviar mensaje";
|
||||
|
||||
@@ -1645,10 +1660,10 @@
|
||||
"Group welcome message" = "Mensaje de bienvenida en grupos";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group will be deleted for all members - this cannot be undone!" = "El grupo se eliminará para todos los miembros. ¡No podrá deshacerse!";
|
||||
"Group will be deleted for all members - this cannot be undone!" = "El grupo será eliminado para todos los miembros. ¡No podrá deshacerse!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Group will be deleted for you - this cannot be undone!" = "El grupo se eliminará para tí. ¡No podrá deshacerse!";
|
||||
"Group will be deleted for you - this cannot be undone!" = "El grupo será eliminado para tí. ¡No podrá deshacerse!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Help" = "Ayuda";
|
||||
@@ -2121,6 +2136,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"New database archive" = "Nuevo archivo de bases de datos";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"New desktop app!" = "Nueva aplicación para PC!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"New display name" = "Nuevo nombre mostrado";
|
||||
|
||||
@@ -2197,7 +2215,8 @@
|
||||
"observer" = "observador";
|
||||
|
||||
/* enabled status
|
||||
group pref value */
|
||||
group pref value
|
||||
time to disappear */
|
||||
"off" = "desactivado";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2278,6 +2297,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Only your contact can send voice messages." = "Sólo tu contacto puede enviar mensajes de voz.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open" = "Abrir";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open chat" = "Abrir chat";
|
||||
|
||||
@@ -2296,12 +2318,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Opening database…" = "Abriendo base de datos…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red." = "Abrir el enlace en el navegador puede reducir la privacidad y seguridad de la conexión. Los enlaces SimpleX que no son de confianza aparecerán en rojo.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"or chat with the developers" = "o contacta mediante Chat con los desarrolladores";
|
||||
|
||||
/* member role */
|
||||
"owner" = "propietario";
|
||||
|
||||
@@ -2752,9 +2768,15 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Send delivery receipts to" = "Enviar confirmaciones de entrega a";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"send direct message" = "Enviar mensaje directo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message" = "Enviar mensaje directo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send direct message to connect" = "Enviar mensaje directo para conectar";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Send disappearing message" = "Enviar mensaje temporal";
|
||||
|
||||
@@ -2935,6 +2957,9 @@
|
||||
/* simplex link type */
|
||||
"SimpleX one-time invitation" = "Invitación SimpleX de un uso";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Simplified incognito mode" = "Modo incógnito simplificado";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Skip" = "Omitir";
|
||||
|
||||
@@ -3082,9 +3107,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "El cifrado funciona y un cifrado nuevo no es necesario. ¡Podría dar lugar a errores de conexión!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The group is fully decentralized – it is visible only to the members." = "El grupo está totalmente descentralizado y sólo es visible para los miembros.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The hash of the previous message is different." = "El hash del mensaje anterior es diferente.";
|
||||
|
||||
@@ -3181,6 +3203,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Para comprobar el cifrado de extremo a extremo con tu contacto compara (o escanea) el código en tus dispositivos.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Toggle incognito when connecting." = "Activa incógnito al conectar.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Transport isolation" = "Aislamiento de transporte";
|
||||
|
||||
@@ -3574,9 +3599,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Se te pedirá identificarte cuándo inicies o continues usando la aplicación tras 30 segundos en segundo plano.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You will join a group this link refers to and connect to its group members." = "Te unirás al grupo al que hace referencia este enlace y te conectarás con sus miembros.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You will still receive calls and notifications from muted profiles when they are active." = "Seguirás recibiendo llamadas y notificaciones de los perfiles silenciados cuando estén activos.";
|
||||
|
||||
@@ -3607,9 +3629,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat database is not encrypted - set passphrase to encrypt it." = "La base de datos no está cifrada - establece una contraseña para cifrarla.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profile will be sent to group members" = "Tu perfil será enviado a los miembros del grupo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your chat profiles" = "Mis perfiles";
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user